From ef2bd10f54d7a577764ad81e2b9d6a6f5c4e610a Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Fri, 6 Mar 2026 10:29:57 -0800 Subject: [PATCH 01/36] Changes for azure-ai-projects release v2.0.1 --- sdk/ai/azure-ai-projects/CHANGELOG.md | 22 +++++++++++++++++++ .../azure/ai/projects/_version.py | 2 +- sdk/ai/azure-ai-projects/tsp-location.yaml | 4 ++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 sdk/ai/azure-ai-projects/tsp-location.yaml diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 4c8384d88d48..776a2a6f6b17 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -1,5 +1,27 @@ # Release History +## 2.0.1 (Unreleased) + +### Features Added + +* Placeholder + +### Breaking Changes + +* Placeholder + +### Bug Fixes + +* Placeholder + +### Sample updates + +* Placeholder + +### Other Changes + +* Placeholder + ## 2.0.0 (2026-03-06) First stable release of the client library that uses the Generally Available (GA) version "v1" of the Foundry REST APIs. diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py index 8f2350dd3b0c..38c04a589ff2 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "2.0.0" +VERSION = "2.0.1" diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml new file mode 100644 index 000000000000..d614f684d1c5 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -0,0 +1,4 @@ +directory: specification/ai-foundry/data-plane/Foundry +commit: 05c0bec52a73ca6cd87c700ea6f4d5f68b9b433b +repo: Azure/azure-rest-api-specs +additionalDirectories: From 38e2bfea780875b4088aa170366d1d3639f12401 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Fri, 6 Mar 2026 11:51:11 -0800 Subject: [PATCH 02/36] x --- sdk/ai/azure-ai-projects/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 776a2a6f6b17..7676c008b571 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -10,7 +10,7 @@ * Placeholder -### Bug Fixes +### Bugs Fixed * Placeholder From 95df96439f9839eb7cb81d1d1ac46bec18457ff4 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:56:01 -0800 Subject: [PATCH 03/36] Set beta version --- sdk/ai/azure-ai-projects/CHANGELOG.md | 2 +- sdk/ai/azure-ai-projects/azure/ai/projects/_version.py | 2 +- sdk/ai/azure-ai-projects/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 7676c008b571..34c72e0a6fa5 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -1,6 +1,6 @@ # Release History -## 2.0.1 (Unreleased) +## 2.0.1b1 (Unreleased) ### Features Added diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py index 38c04a589ff2..f70bfecdef88 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "2.0.1" +VERSION = "2.0.1b1" diff --git a/sdk/ai/azure-ai-projects/pyproject.toml b/sdk/ai/azure-ai-projects/pyproject.toml index f5babde606a2..e2ae27e26534 100644 --- a/sdk/ai/azure-ai-projects/pyproject.toml +++ b/sdk/ai/azure-ai-projects/pyproject.toml @@ -17,7 +17,7 @@ authors = [ description = "Microsoft Corporation Azure AI Projects Client Library for Python" license = "MIT" classifiers = [ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", From d04d08b088fbec0afe44f84bf84c24d2e043a233 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Mon, 9 Mar 2026 07:51:50 -0700 Subject: [PATCH 04/36] Re-emit --- .../azure-ai-projects/apiview-properties.json | 3 +- .../ai/projects/aio/operations/_operations.py | 339 ++++++++++++++- .../azure/ai/projects/models/__init__.py | 2 + .../azure/ai/projects/models/_models.py | 50 ++- .../ai/projects/operations/_operations.py | 402 ++++++++++++++++++ sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 6 files changed, 789 insertions(+), 9 deletions(-) diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json index bd38f0ef92c5..e57c56c7a12e 100644 --- a/sdk/ai/azure-ai-projects/apiview-properties.json +++ b/sdk/ai/azure-ai-projects/apiview-properties.json @@ -102,6 +102,7 @@ "azure.ai.projects.models.ScheduleTask": "Azure.AI.Projects.ScheduleTask", "azure.ai.projects.models.EvaluationScheduleTask": "Azure.AI.Projects.EvaluationScheduleTask", "azure.ai.projects.models.EvaluationTaxonomy": "Azure.AI.Projects.EvaluationTaxonomy", + "azure.ai.projects.models.EvaluatorCredentialRequest": "Azure.AI.Projects.EvaluatorCredentialRequest", "azure.ai.projects.models.EvaluatorMetric": "Azure.AI.Projects.EvaluatorMetric", "azure.ai.projects.models.EvaluatorVersion": "Azure.AI.Projects.EvaluatorVersion", "azure.ai.projects.models.FabricDataAgentToolParameters": "Azure.AI.Projects.FabricDataAgentToolParameters", @@ -220,6 +221,7 @@ "azure.ai.projects.models.EvaluatorDefinitionType": "Azure.AI.Projects.EvaluatorDefinitionType", "azure.ai.projects.models.EvaluatorMetricType": "Azure.AI.Projects.EvaluatorMetricType", "azure.ai.projects.models.EvaluatorMetricDirection": "Azure.AI.Projects.EvaluatorMetricDirection", + "azure.ai.projects.models.PendingUploadType": "Azure.AI.Projects.PendingUploadType", "azure.ai.projects.models.OperationState": "Azure.Core.Foundations.OperationState", "azure.ai.projects.models.InsightType": "Azure.AI.Projects.InsightType", "azure.ai.projects.models.SampleType": "Azure.AI.Projects.SampleType", @@ -260,7 +262,6 @@ "azure.ai.projects.models.ConnectionType": "Azure.AI.Projects.ConnectionType", "azure.ai.projects.models.CredentialType": "Azure.AI.Projects.CredentialType", "azure.ai.projects.models.DatasetType": "Azure.AI.Projects.DatasetType", - "azure.ai.projects.models.PendingUploadType": "Azure.AI.Projects.PendingUploadType", "azure.ai.projects.models.DeploymentType": "Azure.AI.Projects.DeploymentType", "azure.ai.projects.models.IndexType": "Azure.AI.Projects.IndexType", "azure.ai.projects.models.MemoryStoreUpdateStatus": "Azure.AI.Projects.MemoryStoreUpdateStatus", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 55291e178dd2..f5d68ab4c376 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -57,9 +57,11 @@ build_beta_evaluation_taxonomies_update_request, build_beta_evaluators_create_version_request, build_beta_evaluators_delete_version_request, + build_beta_evaluators_get_credentials_request, build_beta_evaluators_get_version_request, build_beta_evaluators_list_request, build_beta_evaluators_list_versions_request, + build_beta_evaluators_pending_upload_request, build_beta_evaluators_update_version_request, build_beta_insights_generate_request, build_beta_insights_get_request, @@ -110,9 +112,8 @@ T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]] List = list -_SERIALIZER = Serializer() -_SERIALIZER.client_side_validation = False - +REM _SERIALIZER = Serializer() +REM _SERIALIZER.client_side_validation = False class BetaOperations: """ @@ -4662,6 +4663,338 @@ async def update_version( return deserialized # type: ignore + @overload + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: _models.PendingUploadRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Is one of the following + types: PendingUploadRequest, JSON, IO[bytes] Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or + IO[bytes] + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(pending_upload_request, (IOBase, bytes)): + _content = pending_upload_request + else: + _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluators_pending_upload_request( + name=name, + version=version, + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.PendingUploadResponse, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @overload + async def get_credentials( + self, + name: str, + version: str, + credential_request: _models.EvaluatorCredentialRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def get_credentials( + self, + name: str, + version: str, + credential_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def get_credentials( + self, + name: str, + version: str, + credential_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def get_credentials( + self, + name: str, + version: str, + credential_request: Union[_models.EvaluatorCredentialRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Is one of the following types: + EvaluatorCredentialRequest, JSON, IO[bytes] Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest or JSON or + IO[bytes] + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(credential_request, (IOBase, bytes)): + _content = credential_request + else: + _content = json.dumps(credential_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluators_get_credentials_request( + name=name, + version=version, + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.DatasetCredential, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + class BetaInsightsOperations: """ diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py index bb537cc97636..110c948952f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py @@ -102,6 +102,7 @@ EvaluationScheduleTask, EvaluationTaxonomy, EvaluationTaxonomyInput, + EvaluatorCredentialRequest, EvaluatorDefinition, EvaluatorMetric, EvaluatorVersion, @@ -372,6 +373,7 @@ "EvaluationScheduleTask", "EvaluationTaxonomy", "EvaluationTaxonomyInput", + "EvaluatorCredentialRequest", "EvaluatorDefinition", "EvaluatorMetric", "EvaluatorVersion", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index 7e7b260b7626..bae28a926cec 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -2230,23 +2230,35 @@ class CodeBasedEvaluatorDefinition(EvaluatorDefinition, discriminator="code"): :vartype metrics: dict[str, ~azure.ai.projects.models.EvaluatorMetric] :ivar type: Required. Code-based definition. :vartype type: str or ~azure.ai.projects.models.CODE - :ivar code_text: Inline code text for the evaluator. Required. + :ivar code_text: Inline code text for the evaluator. :vartype code_text: str + :ivar entry_point: The entry point Python file name for the uploaded evaluator code (e.g. + 'answer_length_evaluator.py'). + :vartype entry_point: str + :ivar image_tag: The container image tag to use for evaluator code execution. + :vartype image_tag: str """ type: Literal[EvaluatorDefinitionType.CODE] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """Required. Code-based definition.""" - code_text: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) - """Inline code text for the evaluator. Required.""" + code_text: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Inline code text for the evaluator.""" + entry_point: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The entry point Python file name for the uploaded evaluator code (e.g. + 'answer_length_evaluator.py').""" + image_tag: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The container image tag to use for evaluator code execution.""" @overload def __init__( self, *, - code_text: str, init_parameters: Optional[dict[str, Any]] = None, data_schema: Optional[dict[str, Any]] = None, metrics: Optional[dict[str, "_models.EvaluatorMetric"]] = None, + code_text: Optional[str] = None, + entry_point: Optional[str] = None, + image_tag: Optional[str] = None, ) -> None: ... @overload @@ -4243,6 +4255,36 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) +class EvaluatorCredentialRequest(_Model): + """Request body for getting evaluator credentials. + + :ivar blob_uri: The blob URI for the evaluator storage. Example: + ``https://account.blob.core.windows.net:443/container``. Required. + :vartype blob_uri: str + """ + + blob_uri: str = rest_field(name="blobUri", visibility=["read", "create", "update", "delete", "query"]) + """The blob URI for the evaluator storage. Example: + ``https://account.blob.core.windows.net:443/container``. Required.""" + + @overload + def __init__( + self, + *, + blob_uri: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + class EvaluatorMetric(_Model): """Evaluator Metric. diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index 371aa81b2b00..21502f0de2bb 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -1255,6 +1255,76 @@ def build_beta_evaluators_update_version_request( # pylint: disable=name-too-lo return HttpRequest(method="PATCH", url=_url, params=_params, headers=_headers, **kwargs) +def build_beta_evaluators_pending_upload_request( # pylint: disable=name-too-long + name: str, + version: str, + *, + foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/evaluators/{name}/versions/{version}/startPendingUpload" + path_format_arguments = { + "name": _SERIALIZER.url("name", name, "str"), + "version": _SERIALIZER.url("version", version, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_beta_evaluators_get_credentials_request( # pylint: disable=name-too-long + name: str, + version: str, + *, + foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/evaluators/{name}/versions/{version}/credentials" + path_format_arguments = { + "name": _SERIALIZER.url("name", name, "str"), + "version": _SERIALIZER.url("version", version, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + def build_beta_insights_generate_request( *, foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW], **kwargs: Any ) -> HttpRequest: @@ -6394,6 +6464,338 @@ def update_version( return deserialized # type: ignore + @overload + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: _models.PendingUploadRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Is one of the following + types: PendingUploadRequest, JSON, IO[bytes] Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or + IO[bytes] + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(pending_upload_request, (IOBase, bytes)): + _content = pending_upload_request + else: + _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluators_pending_upload_request( + name=name, + version=version, + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.PendingUploadResponse, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @overload + def get_credentials( + self, + name: str, + version: str, + credential_request: _models.EvaluatorCredentialRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def get_credentials( + self, + name: str, + version: str, + credential_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def get_credentials( + self, + name: str, + version: str, + credential_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def get_credentials( + self, + name: str, + version: str, + credential_request: Union[_models.EvaluatorCredentialRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Is one of the following types: + EvaluatorCredentialRequest, JSON, IO[bytes] Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest or JSON or + IO[bytes] + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(credential_request, (IOBase, bytes)): + _content = credential_request + else: + _content = json.dumps(credential_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluators_get_credentials_request( + name=name, + version=version, + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.DatasetCredential, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + class BetaInsightsOperations: """ diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index d614f684d1c5..52212df81322 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: 05c0bec52a73ca6cd87c700ea6f4d5f68b9b433b +commit: b09cb5a69be1c014d9f67f463d6ede22035b1088 repo: Azure/azure-rest-api-specs additionalDirectories: From 06c4c26936c96ef4609c3617b4671890a693f66b Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Mon, 9 Mar 2026 07:55:13 -0700 Subject: [PATCH 05/36] fix --- .../azure/ai/projects/aio/operations/_operations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index f5d68ab4c376..6f2e077a1e91 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -112,8 +112,8 @@ T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]] List = list -REM _SERIALIZER = Serializer() -REM _SERIALIZER.client_side_validation = False +_SERIALIZER = Serializer() +_SERIALIZER.client_side_validation = False class BetaOperations: """ From 86d53c53e8ffa1fac49c9f7388ee1b3f9e9f321f Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Mon, 9 Mar 2026 17:54:18 -0700 Subject: [PATCH 06/36] Rename env varrs name --- sdk/ai/azure-ai-projects/.env.template | 4 +- sdk/ai/azure-ai-projects/CHANGELOG.md | 4 +- sdk/ai/azure-ai-projects/README.md | 18 ++-- .../samples/agents/sample_agent_basic.py | 8 +- .../agents/sample_agent_basic_async.py | 8 +- .../agents/sample_agent_retrieve_basic.py | 8 +- .../sample_agent_retrieve_basic_async.py | 8 +- .../agents/sample_agent_stream_events.py | 8 +- .../agents/sample_agent_structured_output.py | 8 +- .../sample_agent_structured_output_async.py | 8 +- .../agents/sample_workflow_multi_agent.py | 10 +-- .../sample_workflow_multi_agent_async.py | 10 +-- ..._agent_basic_with_azure_monitor_tracing.py | 8 +- ...sample_agent_basic_with_console_tracing.py | 8 +- ..._with_console_tracing_custom_attributes.py | 8 +- .../agents/tools/sample_agent_ai_search.py | 8 +- .../tools/sample_agent_azure_function.py | 8 +- .../tools/sample_agent_bing_custom_search.py | 8 +- .../tools/sample_agent_bing_grounding.py | 8 +- .../tools/sample_agent_browser_automation.py | 8 +- .../tools/sample_agent_code_interpreter.py | 8 +- .../sample_agent_code_interpreter_async.py | 8 +- ...ample_agent_code_interpreter_with_files.py | 8 +- ...agent_code_interpreter_with_files_async.py | 8 +- .../agents/tools/sample_agent_computer_use.py | 4 +- .../tools/sample_agent_computer_use_async.py | 4 +- .../agents/tools/sample_agent_fabric.py | 8 +- .../agents/tools/sample_agent_file_search.py | 8 +- .../sample_agent_file_search_in_stream.py | 8 +- ...ample_agent_file_search_in_stream_async.py | 8 +- .../tools/sample_agent_function_tool.py | 8 +- .../tools/sample_agent_function_tool_async.py | 8 +- .../tools/sample_agent_image_generation.py | 8 +- .../sample_agent_image_generation_async.py | 8 +- .../samples/agents/tools/sample_agent_mcp.py | 8 +- .../agents/tools/sample_agent_mcp_async.py | 8 +- ...ample_agent_mcp_with_project_connection.py | 8 +- ...agent_mcp_with_project_connection_async.py | 8 +- .../tools/sample_agent_memory_search.py | 8 +- .../tools/sample_agent_memory_search_async.py | 8 +- .../agents/tools/sample_agent_openapi.py | 8 +- ...e_agent_openapi_with_project_connection.py | 8 +- .../agents/tools/sample_agent_sharepoint.py | 8 +- .../agents/tools/sample_agent_to_agent.py | 8 +- .../agents/tools/sample_agent_web_search.py | 8 +- .../tools/sample_agent_web_search_preview.py | 8 +- ...ple_agent_web_search_with_custom_search.py | 8 +- .../samples/connections/sample_connections.py | 4 +- .../connections/sample_connections_async.py | 4 +- .../samples/datasets/sample_datasets.py | 4 +- .../samples/datasets/sample_datasets_async.py | 4 +- .../datasets/sample_datasets_download.py | 4 +- .../samples/deployments/sample_deployments.py | 8 +- .../deployments/sample_deployments_async.py | 8 +- .../samples/evaluations/README.md | 8 +- .../agentic_evaluators/sample_coherence.py | 8 +- .../agentic_evaluators/sample_fluency.py | 8 +- .../agent_utils.py | 2 +- .../sample_generic_agentic_evaluator.py | 6 +- .../agentic_evaluators/sample_groundedness.py | 8 +- .../sample_intent_resolution.py | 8 +- .../agentic_evaluators/sample_relevance.py | 8 +- .../sample_response_completeness.py | 8 +- .../sample_task_adherence.py | 8 +- .../sample_task_completion.py | 8 +- .../sample_task_navigation_efficiency.py | 4 +- .../sample_tool_call_accuracy.py | 8 +- .../sample_tool_call_success.py | 8 +- .../sample_tool_input_accuracy.py | 8 +- .../sample_tool_output_utilization.py | 8 +- .../sample_tool_selection.py | 8 +- .../evaluations/sample_agent_evaluation.py | 8 +- .../sample_agent_response_evaluation.py | 8 +- ..._response_evaluation_with_function_tool.py | 8 +- .../sample_continuous_evaluation_rule.py | 8 +- .../evaluations/sample_eval_catalog.py | 4 +- ...mple_eval_catalog_code_based_evaluators.py | 8 +- ...le_eval_catalog_prompt_based_evaluators.py | 8 +- .../sample_evaluation_cluster_insight.py | 10 +-- .../sample_evaluation_compare_insight.py | 8 +- .../sample_evaluations_ai_assisted.py | 8 +- ...ple_evaluations_builtin_with_dataset_id.py | 8 +- ...le_evaluations_builtin_with_inline_data.py | 8 +- ...valuations_builtin_with_inline_data_oai.py | 8 +- .../sample_evaluations_builtin_with_traces.py | 8 +- .../evaluations/sample_evaluations_graders.py | 8 +- ...aluations_score_model_grader_with_image.py | 8 +- .../evaluations/sample_model_evaluation.py | 8 +- .../evaluations/sample_redteam_evaluations.py | 6 +- .../sample_scheduled_evaluations.py | 12 +-- .../samples/files/sample_files.py | 4 +- .../samples/files/sample_files_async.py | 4 +- .../finetuning/sample_finetuning_dpo_job.py | 4 +- .../sample_finetuning_dpo_job_async.py | 4 +- ...le_finetuning_oss_models_supervised_job.py | 4 +- ...etuning_oss_models_supervised_job_async.py | 4 +- .../sample_finetuning_reinforcement_job.py | 4 +- ...mple_finetuning_reinforcement_job_async.py | 4 +- .../sample_finetuning_supervised_job.py | 4 +- .../sample_finetuning_supervised_job_async.py | 4 +- .../samples/indexes/sample_indexes.py | 4 +- .../samples/indexes/sample_indexes_async.py | 4 +- .../mcp_client/sample_mcp_tool_async.py | 6 +- .../memories/sample_memory_advanced.py | 4 +- .../memories/sample_memory_advanced_async.py | 4 +- .../samples/memories/sample_memory_basic.py | 4 +- .../memories/sample_memory_basic_async.py | 4 +- .../samples/memories/sample_memory_crud.py | 4 +- .../memories/sample_memory_crud_async.py | 4 +- .../samples/red_team/sample_red_team.py | 8 +- .../samples/red_team/sample_red_team_async.py | 8 +- .../responses/sample_responses_basic.py | 10 +-- .../responses/sample_responses_basic_async.py | 10 +-- ...responses_basic_without_aiprojectclient.py | 8 +- ...ses_basic_without_aiprojectclient_async.py | 8 +- .../responses/sample_responses_image_input.py | 8 +- .../sample_responses_stream_events.py | 8 +- .../sample_responses_stream_manager.py | 8 +- .../sample_responses_structured_output.py | 8 +- .../samples/telemetry/sample_telemetry.py | 4 +- .../telemetry/sample_telemetry_async.py | 4 +- .../telemetry/test_ai_agents_instrumentor.py | 8 +- .../test_ai_agents_instrumentor_async.py | 8 +- .../telemetry/test_responses_instrumentor.py | 84 +++++++++---------- .../test_responses_instrumentor_async.py | 72 ++++++++-------- ...sponses_instrumentor_browser_automation.py | 8 +- ...s_instrumentor_browser_automation_async.py | 8 +- ...responses_instrumentor_code_interpreter.py | 8 +- ...ses_instrumentor_code_interpreter_async.py | 8 +- ...test_responses_instrumentor_file_search.py | 8 +- ...esponses_instrumentor_file_search_async.py | 8 +- .../test_responses_instrumentor_mcp.py | 8 +- .../test_responses_instrumentor_mcp_async.py | 8 +- .../test_responses_instrumentor_metrics.py | 2 +- .../test_responses_instrumentor_workflow.py | 8 +- ...t_responses_instrumentor_workflow_async.py | 8 +- .../tests/agents/test_agent_responses_crud.py | 4 +- .../agents/test_agent_responses_crud_async.py | 4 +- .../tests/agents/test_agents_crud.py | 2 +- .../tests/agents/test_agents_crud_async.py | 2 +- ...est_agent_code_interpreter_and_function.py | 4 +- ..._agent_file_search_and_code_interpreter.py | 4 +- .../test_agent_file_search_and_function.py | 8 +- ...t_file_search_code_interpreter_function.py | 2 +- .../test_multitool_with_conversations.py | 2 +- .../agents/tools/test_agent_ai_search.py | 2 +- .../tools/test_agent_ai_search_async.py | 2 +- .../agents/tools/test_agent_bing_grounding.py | 4 +- .../tools/test_agent_code_interpreter.py | 4 +- .../test_agent_code_interpreter_async.py | 2 +- .../agents/tools/test_agent_file_search.py | 4 +- .../tools/test_agent_file_search_async.py | 4 +- .../tools/test_agent_file_search_stream.py | 2 +- .../test_agent_file_search_stream_async.py | 2 +- .../agents/tools/test_agent_function_tool.py | 6 +- .../tools/test_agent_function_tool_async.py | 6 +- .../tools/test_agent_image_generation.py | 2 +- .../test_agent_image_generation_async.py | 2 +- .../tests/agents/tools/test_agent_mcp.py | 4 +- .../agents/tools/test_agent_mcp_async.py | 2 +- .../agents/tools/test_agent_memory_search.py | 2 +- .../tools/test_agent_memory_search_async.py | 2 +- .../tests/agents/tools/test_agent_openapi.py | 2 +- .../agents/tools/test_agent_openapi_async.py | 2 +- .../test_agent_tools_with_conversations.py | 8 +- .../agents/tools/test_agent_web_search.py | 2 +- .../tools/test_agent_web_search_async.py | 2 +- .../tests/datasets/test_datasets.py | 2 +- .../tests/datasets/test_datasets_async.py | 2 +- .../tests/deployments/test_deployments.py | 4 +- .../deployments/test_deployments_async.py | 4 +- .../tests/finetuning/test_finetuning.py | 4 +- .../tests/finetuning/test_finetuning_async.py | 4 +- .../tests/responses/test_responses.py | 2 +- .../tests/responses/test_responses_async.py | 2 +- .../azure-ai-projects/tests/samples/README.md | 12 +-- .../tests/samples/test_samples.py | 32 +++---- .../tests/samples/test_samples_async.py | 28 +++---- .../tests/samples/test_samples_evaluations.py | 16 ++-- sdk/ai/azure-ai-projects/tests/test_base.py | 12 +-- 180 files changed, 668 insertions(+), 666 deletions(-) diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index df66f2a71199..89e81956f322 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -20,8 +20,8 @@ AZURE_AI_PROJECTS_CONSOLE_LOGGING= # Project endpoint has the format: # `https://.services.ai.azure.com/api/projects/` -AZURE_AI_PROJECT_ENDPOINT= -AZURE_AI_MODEL_DEPLOYMENT_NAME= +PROJECT_ENDPOINT= +MODEL_DEPLOYMENT_NAME= AZURE_AI_AGENT_NAME= CONVERSATION_ID= CONNECTION_NAME= diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 34c72e0a6fa5..80b7bcd0be13 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -20,7 +20,9 @@ ### Other Changes -* Placeholder +* Replace environment variable name, `AZURE_AI_PROJECT_ENDPOINT` TO `PROJECT_ENDPOINT`. +* Replace environment variable name, `AZURE_AI_MODEL_DEPLOYMENT_NAME` TO `MODEL_DEPLOYMENT_NAME`. + ## 2.0.0 (2026-03-06) diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 8c741a297add..a9b17e078b94 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -54,7 +54,7 @@ To report an issue with the client library, or request additional features, plea * Python 3.9 or later. * An [Azure subscription][azure_sub]. * A [project in Microsoft Foundry](https://learn.microsoft.com/azure/foundry/how-to/create-projects). -* A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `AZURE_AI_PROJECT_ENDPOINT` was defined to hold this value. +* A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `PROJECT_ENDPOINT` was defined to hold this value. * An Entra ID token for authentication. Your application needs an object that implements the [TokenCredential](https://learn.microsoft.com/python/api/azure-core/azure.core.credentials.tokencredential) interface. Code samples here use [DefaultAzureCredential](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential). To get that working, you will need: * An appropriate role assignment. See [Role-based access control in Microsoft Foundry portal](https://learn.microsoft.com/azure/foundry/concepts/rbac-foundry). Role assignment can be done via the "Access Control (IAM)" tab of your Azure AI Project resource in the Azure portal. * [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed. @@ -87,7 +87,7 @@ from azure.identity import DefaultAzureCredential with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, ): ``` @@ -107,7 +107,7 @@ from azure.identity.aio import DefaultAzureCredential async with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, ): ``` @@ -117,20 +117,20 @@ async with ( Your Microsoft Foundry project may have one or more AI models deployed. These could be OpenAI models, Microsoft models, or models from other providers. Use the code below to get an authenticated [OpenAI](https://github.com/openai/openai-python?tab=readme-ov-file#usage) client from the [openai](https://pypi.org/project/openai/) package, and execute an example multi-turn "Responses" calls. -The code below assumes the environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). +The code below assumes the environment variable `MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). ```python with project_client.get_openai_client() as openai_client: response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) @@ -145,7 +145,7 @@ See the "responses" folder in the [package samples][samples] for additional samp The `.agents` property on the `AIProjectClient` gives you access to all Agent operations. Agents use an extension of the OpenAI Responses protocol, so you will need to get an `OpenAI` client to do Agent operations, as shown in the example below. -The code below assumes environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). +The code below assumes environment variable `MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). See the "agents" folder in the [package samples][samples] for an extensive set of samples, including streaming, tool usage and memory store usage. @@ -156,7 +156,7 @@ with project_client.get_openai_client() as openai_client: agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) @@ -1357,7 +1357,7 @@ By default logs redact the values of URL query strings, the values of some HTTP ```python project_client = AIProjectClient( credential=DefaultAzureCredential(), - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + endpoint=os.environ["PROJECT_ENDPOINT"], logging_enable=True ) ``` diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py index 1b57d0bcd29c..a494cd33cd1e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py index 69404d31ae35..c4ddae1518c3 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main() -> None: @@ -48,7 +48,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions.", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py index 876dc8daebcd..89d6192b6c7b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py @@ -22,9 +22,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -36,8 +36,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["PROJECT_ENDPOINT"] +model = os.environ["MODEL_DEPLOYMENT_NAME"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py index 8baa7034c139..89320902dd05 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py @@ -22,9 +22,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -37,8 +37,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["PROJECT_ENDPOINT"] +model = os.environ["MODEL_DEPLOYMENT_NAME"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py index 5ab97c36b587..cfb11d16d92b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py index dfeb5c961720..83f7e7803384 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py @@ -24,9 +24,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv pydantic Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -51,7 +51,7 @@ class CalendarEvent(BaseModel): participants: list[str] -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -62,7 +62,7 @@ class CalendarEvent(BaseModel): agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], text=PromptAgentDefinitionTextOptions( format=TextResponseFormatJsonSchema(name="CalendarEvent", schema=CalendarEvent.model_json_schema()) ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py index 12bdaf31231a..a21d54e8426d 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py @@ -24,9 +24,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp pydantic Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] class CalendarEvent(BaseModel): @@ -63,7 +63,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], text=PromptAgentDefinitionTextOptions( format=TextResponseFormatJsonSchema(name="CalendarEvent", schema=CalendarEvent.model_json_schema()) ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py index b8674dc5c146..5def14deed3e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ teacher_agent = project_client.agents.create_version( agent_name="teacher-agent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", @@ -58,7 +58,7 @@ student_agent = project_client.agents.create_version( agent_name="student-agent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py index b565a04be074..160e059dbd39 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -36,7 +36,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main(): @@ -50,7 +50,7 @@ async def main(): teacher_agent = await project_client.agents.create_version( agent_name="teacher-agent-async", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", @@ -61,7 +61,7 @@ async def main(): student_agent = await project_client.agents.create_version( agent_name="student-agent-async", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py index 0f318459182f..cdf10921d9a0 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-monitor-opentelemetry Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -46,7 +46,7 @@ with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, ): # [START setup_azure_monitor_tracing] # Enable Azure Monitor tracing @@ -62,7 +62,7 @@ # [END create_span_for_scenario] with project_client.get_openai_client() as openai_client: agent_definition = PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py index e5aa0582e9a6..5495550a2131 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv opentelemetry-sdk azure-core-tracing-opentelemetry Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -90,11 +90,11 @@ def display_conversation_item(item: Any) -> None: # [END create_span_for_scenario] with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, project_client.get_openai_client() as openai_client, ): agent_definition = PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py index 64786e3a00e0..c35bc71b8823 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv opentelemetry-sdk azure-core-tracing-opentelemetry Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] # Define the custom span processor that is used for adding the custom @@ -94,7 +94,7 @@ def on_end(self, span: ReadableSpan): ): agent_definition = PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py index 6f082269687a..e0e5cc4267ea 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AI_SEARCH_PROJECT_CONNECTION_ID - The AI Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -66,7 +66,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a helpful assistant. You must always provide citations for answers using the tool and render them as: `\u3010message_idx:search_idx\u2020source\u3011`.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py index 242ee742229b..d6f5aed57571 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0b1" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) STORAGE_INPUT_QUEUE_NAME - The name of the Azure Storage Queue to use for input and output in the Azure Function tool. 4) STORAGE_OUTPUT_QUEUE_NAME - The name of the Azure Storage Queue to use for output in the Azure Function tool. @@ -44,7 +44,7 @@ agent = None -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -82,7 +82,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py index 1ebf4c6d213b..43c3246fb6f9 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py @@ -27,9 +27,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID - The Bing Custom Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -50,7 +50,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -74,7 +74,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a helpful agent that can use Bing Custom Search tools to assist users. Use the available Bing Custom Search tools to answer questions and perform tasks.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py index 22fb479f0109..7ca03afa983a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py @@ -35,9 +35,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_PROJECT_CONNECTION_ID - The Bing project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. """ @@ -55,7 +55,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -76,7 +76,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py index aa660411a6cc..38129dea7b78 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BROWSER_AUTOMATION_PROJECT_CONNECTION_ID - The browser automation project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are an Agent helping with browser automation tasks. You can answer questions, provide information, and assist with various tasks related to web browsing using the Browser Automation tool available to you.""", diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py index 6007468f8439..72f9d84a4aa9 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -47,7 +47,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py index edde775f47aa..de936fcfff6b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main() -> None: @@ -46,7 +46,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[CodeInterpreterTool()], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py index 3cb511cfcc3b..972d9ad84447 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -57,7 +57,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py index 69ddd048bcc8..e275ab430db7 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main() -> None: @@ -59,7 +59,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=[file.id]))], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py index 3b1ec849257c..ec91666a1fa2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py @@ -23,7 +23,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) (Optional) COMPUTER_USE_MODEL_DEPLOYMENT_NAME - The deployment name of the computer-use-preview model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -45,7 +45,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py index c2c85a6c9906..65b027d6e281 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py @@ -23,7 +23,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) (Optional) COMPUTER_USE_MODEL_DEPLOYMENT_NAME - The deployment name of the computer-use-preview model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py index 20bcd70f597d..c780ff71b510 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) FABRIC_PROJECT_CONNECTION_ID - The Fabric project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -59,7 +59,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py index 9e14fdd34461..c92e0cfc1e0f 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search through product information.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py index 4bac22ce828c..c8ce27101fc2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -63,7 +63,7 @@ agent = project_client.agents.create_version( agent_name="StreamingFileSearchAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search through product information and provide detailed responses. Use the file search tool to find relevant information before answering.", tools=[FileSearchTool(vector_store_ids=[vector_store.id])], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py index 7f02ea13a7d6..1ca1c6ec2ec5 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main() -> None: @@ -63,7 +63,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="StreamingFileSearchAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search through product information and provide detailed responses. Use the file search tool to find relevant information before answering.", tools=[FileSearchTool(vector_store_ids=[vector_store.id])], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py index 701201d55d99..a8ee3ce16447 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ def get_horoscope(sign: str) -> str: return f"{sign}: Next Tuesday you will befriend a baby otter." -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -69,7 +69,7 @@ def get_horoscope(sign: str) -> str: agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can use function tools.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py index ccbc373e6585..8f7a416abeb1 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def get_horoscope(sign: str) -> str: @@ -70,7 +70,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can use function tools.", tools=[func_tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py index 5b9ea7011b3c..db372ec52647 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py @@ -28,9 +28,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) used by the Agent for understanding and responding to prompts. This is NOT the image generation model. 3) IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME - The deployment name of the image generation model (e.g. gpt-image-1) used by the ImageGenTool. @@ -53,7 +53,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -73,7 +73,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="Generate images based on user prompts", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py index b7859d6483eb..34010e739490 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py @@ -28,9 +28,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) used by the Agent for understanding and responding to prompts. This is NOT the image generation model. 3) IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME - The deployment name of the image generation model (e.g. gpt-image-1) used by the ImageGenTool. @@ -53,7 +53,7 @@ from azure.ai.projects.models import PromptAgentDefinition, ImageGenTool load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main(): @@ -68,7 +68,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="Generate images based on user prompts", tools=[ImageGenTool(model=image_generation_model, quality="low", size="1024x1024")], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py index c318f9004f1e..95a4010ced59 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.", tools=[mcp_tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py index cc12b02a8fdd..0e980be777ad 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main(): @@ -55,7 +55,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.", tools=tools, ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py index be1036a9fde8..27af3ffcd510 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys with key equals to "Authorization" and value to be "Bearer ". @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -55,7 +55,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent7", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="Use MCP tools as needed", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py index 71ec422987a4..7d25ec8836ae 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys with key equals to "Authorization" and value to be "Bearer ". @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main(): @@ -58,7 +58,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="Use MCP tools as needed", tools=tools, ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py index 26f9221631f1..6888ad8263a5 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py @@ -23,9 +23,9 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -48,7 +48,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -96,7 +96,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py index 6e959c0c3a88..dd3792a89249 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py @@ -23,9 +23,9 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -51,7 +51,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, @@ -90,7 +90,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", tools=[ MemorySearchPreviewTool( diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py index aba7820c69d6..33080b31243c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv jsonref Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -38,7 +38,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -65,7 +65,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py index 886288df69de..e4d56f1ba0f6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv jsonref Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) OPENAPI_PROJECT_CONNECTION_ID - The OpenAPI project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -42,7 +42,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -75,7 +75,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py index 6a108dfe83a9..273b6c18a73e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) SHAREPOINT_PROJECT_CONNECTION_ID - The SharePoint project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -59,7 +59,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="""You are a helpful agent that can use SharePoint tools to assist users. Use the available SharePoint tools to answer questions and perform tasks.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py index 133b99d589e0..2a0475de9bfa 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) A2A_PROJECT_CONNECTION_ID - The A2A project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -61,7 +61,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py index 01729d031854..6837cb0b13a2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py @@ -26,9 +26,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -46,7 +46,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search the web", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py index bf09f23786ff..d433fb8cf8e7 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py @@ -26,9 +26,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,7 +42,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -56,7 +56,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent105", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search the web", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py index 41d8d6f75aa2..f086c37c2517 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py @@ -27,9 +27,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID - The Bing Custom Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -51,7 +51,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -70,7 +70,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search the web and bing", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py b/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py index a867acc2106e..f970221e5068 100644 --- a/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py +++ b/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - The name of a connection, as found in the "Connected resources" tab in the Management Center of your Microsoft Foundry project. @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] connection_name = os.environ["CONNECTION_NAME"] with ( diff --git a/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py b/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py index e5814abf27fc..fce1328c7d36 100644 --- a/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py +++ b/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - The name of a connection, as found in the "Connected resources" tab in the Management Center of your Microsoft Foundry project. @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] connection_name = os.environ["CONNECTION_NAME"] diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py index eb664060a12c..c0ef2fb68bf9 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -36,7 +36,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version_1 = os.environ.get("DATASET_VERSION_1", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py index 7740507124d9..ebf84028bbc8 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -45,7 +45,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version_1 = os.environ.get("DATASET_VERSION_1", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py index 2a9aa4fc73a2..138431174b93 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py @@ -19,7 +19,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version = os.environ.get("DATASET_VERSION", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py index cee409590d7a..f6faeb5037bd 100644 --- a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py +++ b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. 3) MODEL_PUBLISHER - Optional. The publisher of the model to filter by. 4) MODEL_NAME - Optional. The name of the model to filter by. """ @@ -31,8 +31,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] model_publisher = os.environ.get("MODEL_PUBLISHER", "Microsoft") model_name = os.environ.get("MODEL_NAME", "Phi-4") diff --git a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py index ae2a1151ba17..00c7d1a3e812 100644 --- a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py +++ b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. 3) MODEL_PUBLISHER - Optional. The publisher of the model to filter by. 4) MODEL_NAME - Optional. The name of the model to filter by. """ @@ -35,8 +35,8 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] - model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] + endpoint = os.environ["PROJECT_ENDPOINT"] + model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] model_publisher = os.environ.get("MODEL_PUBLISHER", "Microsoft") model_name = os.environ.get("MODEL_NAME", "Phi-4") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/README.md b/sdk/ai/azure-ai-projects/samples/evaluations/README.md index 628468aa20e4..078a9edc6cfc 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/README.md +++ b/sdk/ai/azure-ai-projects/samples/evaluations/README.md @@ -11,8 +11,8 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv ``` Set these environment variables: -- `AZURE_AI_PROJECT_ENDPOINT` - Your Azure AI Project endpoint (e.g., `https://.services.ai.azure.com/api/projects/`) -- `AZURE_AI_MODEL_DEPLOYMENT_NAME` - The model deployment name (e.g., `gpt-4o-mini`) +- `PROJECT_ENDPOINT` - Your Azure AI Project endpoint (e.g., `https://.services.ai.azure.com/api/projects/`) +- `MODEL_DEPLOYMENT_NAME` - The model deployment name (e.g., `gpt-4o-mini`) ## Sample Index @@ -94,8 +94,8 @@ Located in the [agentic_evaluators](https://github.com/Azure/azure-sdk-for-pytho ```bash # Set environment variables -export AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" -export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Replace with your model +export PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +export MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Replace with your model # Run a sample python sample_evaluations_builtin_with_inline_data.py diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py index 13bbaf3726bc..19ebfbc845fa 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py index dc0a7199df78..0e747921b2dd 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py index 1650b0d1cc5d..49f206917552 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py @@ -28,7 +28,7 @@ def run_evaluator( data_mapping: dict[str, str], ) -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py index df15b21b6699..d54b7e664ed0 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -36,7 +36,7 @@ def _get_evaluator_initialization_parameters(evaluator_name: str) -> dict[str, s if evaluator_name == "task_navigation_efficiency": return {"matching_mode": "exact_match"} # Can be "exact_match", "in_order_match", or "any_order_match" else: - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini return {"deployment_name": model_deployment_name} diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py index 654adfb1faec..4832d3f1d926 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py index e434a78db732..06861dcb05ea 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py index 1d3cc28dc6cb..0ca65a06c49c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py index cd6ce53055ea..a73734676532 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py index b2f5381c54e4..c884df5ecf08 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py index 92d39f2ddddc..b5439678f8eb 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py index 6c9bc015d529..9383602a6194 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ @@ -42,7 +42,7 @@ def main() -> None: endpoint = os.environ.get( - "AZURE_AI_PROJECT_ENDPOINT", "" + "PROJECT_ENDPOINT", "" ) # Sample : https://.services.ai.azure.com/api/projects/ with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py index c2f8980e503e..c5384ce3c16c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py index 5404c8bb1183..ef82d24014cd 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -44,9 +44,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py index 5809882fb1ee..22dcc2c0f242 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py index 3437f8fd674f..287a36a417f5 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py index e7646aeaabe8..eb21c7c1042e 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py index 7cafd28186f2..b3c6d2a45a27 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py @@ -19,10 +19,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 3) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,8 +39,8 @@ from openai.types.evals.run_retrieve_response import RunRetrieveResponse load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini # [START agent_evaluation_basic] with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py index 9c81014daa52..dae8f5f97596 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py @@ -19,10 +19,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 3) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ agent = project_client.agents.create_version( agent_name=os.environ["AZURE_AI_AGENT_NAME"], definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py index 37d7244c3808..b69f22681c64 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -40,8 +40,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] # Define a function tool for the model to use func_tool = FunctionTool( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py index 7c38cb9a2d5d..7a37829a1110 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py @@ -28,10 +28,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 3) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -50,7 +50,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -63,7 +63,7 @@ agent = project_client.agents.create_version( agent_name=os.environ["AZURE_AI_AGENT_NAME"], definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py index 255ea0c2e660..ff0d08897d8d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py index c3227497204d..abd5526eb45b 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. """ @@ -42,8 +42,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py index b3dfb581b3ce..fc00c7831b57 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. For Custom Prompt Based Evaluators: @@ -75,8 +75,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py index 77093c5d704b..d7a7886e5900 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -51,11 +51,11 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME") if not model_deployment_name: - raise ValueError("AZURE_AI_MODEL_DEPLOYMENT_NAME environment variable is not set") + raise ValueError("MODEL_DEPLOYMENT_NAME environment variable is not set") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py index c0fe4424bc85..b830d1111548 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -47,7 +47,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -64,7 +64,7 @@ TestingCriterionLabelModel( type="label_model", name="sentiment_analysis", - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input=[ { "role": "developer", diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py index 8ad0899d2a01..4d1f0b66289c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -39,8 +39,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py index c113c8b67e06..0aebda63159e 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. 4) DATASET_VERSION - Optional. The version of the Dataset to create and use in this sample. 5) DATA_FOLDER - Optional. The folder path where the data files for upload are located. @@ -44,8 +44,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") dataset_name = os.environ.get("DATASET_NAME", "") dataset_version = os.environ.get("DATASET_VERSION", "1") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py index fa4ec52105a3..1cf2e9328207 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -41,9 +41,9 @@ endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini # Construct the paths to the data folder and data file used in this sample script_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py index 9d968bbbf296..b2d9b79453b6 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py @@ -18,9 +18,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -42,10 +42,10 @@ client = OpenAI( api_key=get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default"), - base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini data_source_config = DataSourceConfigCustom( { diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py index 33aff799eb4f..e24649f8fc78 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py @@ -19,12 +19,12 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-monitor-query Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) APPINSIGHTS_RESOURCE_ID - Required. The Azure Application Insights resource ID that stores agent traces. It has the form: /subscriptions//resourceGroups//providers/Microsoft.Insights/components/. 3) AGENT_ID - Required. The agent identifier emitted by the Azure tracing integration, used to filter traces. - 4) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The Azure OpenAI deployment name to use with the built-in evaluators. + 4) MODEL_DEPLOYMENT_NAME - Required. The Azure OpenAI deployment name to use with the built-in evaluators. 5) TRACE_LOOKBACK_HOURS - Optional. Number of hours to look back when querying traces and in the evaluation run. Defaults to 1. """ @@ -44,12 +44,12 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] appinsights_resource_id = os.environ[ "APPINSIGHTS_RESOURCE_ID" ] # Sample : /subscriptions//resourceGroups//providers/Microsoft.Insights/components/ agent_id = os.environ["AGENT_ID"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] trace_query_hours = int(os.environ.get("TRACE_LOOKBACK_HOURS", "1")) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py index ecb0f7fc86c7..e382f1ce7c3d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -39,8 +39,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py index 0f9377ef9fbf..7f14453d4e26 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv pillow Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -47,8 +47,8 @@ file_path = os.path.abspath(__file__) folder_path = os.path.dirname(file_path) -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") def image_to_data_uri(image_path: str) -> str: diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py index 8b0170031dd3..c66866f7ed27 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -68,7 +68,7 @@ ) print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") - model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] + model = os.environ["MODEL_DEPLOYMENT_NAME"] data_source = { "type": "azure_ai_target_completions", "source": { diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py index 6516c637796e..6f75a6bad70f 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) AZURE_AI_AGENT_NAME - Required. The name of the Agent to perform red teaming evaluation on. """ @@ -45,7 +45,7 @@ def main() -> None: load_dotenv() # - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") + endpoint = os.environ.get("PROJECT_ENDPOINT", "") agent_name = os.environ.get("AZURE_AI_AGENT_NAME", "") with ( @@ -56,7 +56,7 @@ def main() -> None: agent_version = project_client.agents.create_version( agent_name=agent_name, definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py index 29397e006414..fcde3de826ca 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-mgmt-authorization azure-mgmt-resource Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) AZURE_SUBSCRIPTION_ID - Required for RBAC assignment. The Azure subscription ID where the project is located. 3) AZURE_RESOURCE_GROUP_NAME - Required for RBAC assignment. The resource group name where the project is located. @@ -75,13 +75,13 @@ def assign_rbac(): """ load_dotenv() - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") + endpoint = os.environ.get("PROJECT_ENDPOINT", "") subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID", "") resource_group_name = os.environ.get("AZURE_RESOURCE_GROUP_NAME", "") if not endpoint or not subscription_id or not resource_group_name: print( - "Error: AZURE_AI_PROJECT_ENDPOINT, AZURE_SUBSCRIPTION_ID, and AZURE_RESOURCE_GROUP_NAME environment variables are required" + "Error: PROJECT_ENDPOINT, AZURE_SUBSCRIPTION_ID, and AZURE_RESOURCE_GROUP_NAME environment variables are required" ) return @@ -214,7 +214,7 @@ def assign_rbac(): def schedule_dataset_evaluation() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] dataset_name = os.environ.get("DATASET_NAME", "") dataset_version = os.environ.get("DATASET_VERSION", "1") # Construct the paths to the data folder and data file used in this sample @@ -327,7 +327,7 @@ def schedule_dataset_evaluation() -> None: def schedule_redteam_evaluation() -> None: load_dotenv() # - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") + endpoint = os.environ.get("PROJECT_ENDPOINT", "") agent_name = os.environ.get("AZURE_AI_AGENT_NAME", "") # Construct the paths to the data folder and data file used in this sample @@ -343,7 +343,7 @@ def schedule_redteam_evaluation() -> None: agent_version = project_client.agents.create_version( agent_name=agent_name, definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files.py b/sdk/ai/azure-ai-projects/samples/files/sample_files.py index 57f8e3fb3187..ba156aff897e 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files.py @@ -16,7 +16,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) FILE_PATH - Optional. Path to the file to upload. Defaults to the `data` folder. """ @@ -29,7 +29,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] script_dir = Path(__file__).parent file_path = os.environ.get("FILE_PATH", os.path.join(script_dir, "data", "test_file.jsonl")) diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py index d6bb1491a678..0fbdcb80223c 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py @@ -16,7 +16,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) FILE_PATH - Optional. Path to the file to upload. Defaults to the `data` folder. """ @@ -30,7 +30,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] script_dir = Path(__file__).parent file_path = os.environ.get("FILE_PATH", os.path.join(script_dir, "data", "test_file.jsonl")) diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py index b8fe46419490..2e66c9edfb27 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4o` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "dpo_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "dpo_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py index 4e7b5dc91ec0..340159571043 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4o` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "dpo_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "dpo_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py index c4a44e2d727f..5918ff361cdf 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "Ministral-3B") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py index 2fcd5ee8e93c..01267d2d3ecf 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `Ministral-3B` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "Ministral-3B") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py index 6b1bed171863..576587bfb4cc 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `o4-mini` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "o4-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "rft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "rft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py index 701bf384731f..0034551f6005 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `o4-mini` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "o4-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "rft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "rft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py index 626af3c4bedd..4edd2d93f1ee 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py @@ -21,7 +21,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-mgmt-cognitiveservices Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -43,7 +43,7 @@ load_dotenv() # For fine-tuning -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4.1") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py index cd967034fd92..47d3136f2ced 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py @@ -21,7 +21,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp azure-mgmt-cognitiveservices Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -43,7 +43,7 @@ load_dotenv() # For fine-tuning -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4.1") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py index fd865b8d8ca1..594e651a917d 100644 --- a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py +++ b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) INDEX_NAME - Optional. The name of the Index to create and use in this sample. 3) INDEX_VERSION - Optional. The version of the Index to create and use in this sample. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] index_name = os.environ.get("INDEX_NAME", "index-test") index_version = os.environ.get("INDEX_VERSION", "1.0") ai_search_connection_name = os.environ.get("AI_SEARCH_CONNECTION_NAME", "my-ai-search-connection-name") diff --git a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py index 9ace80a10df8..1cd9c00f1328 100644 --- a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py +++ b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) INDEX_NAME - Optional. The name of the Index to create and use in this sample. 3) INDEX_VERSION - Optional. The version of the Index to create and use in this sample. @@ -37,7 +37,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] index_name = os.environ.get("INDEX_NAME", "index-test") index_version = os.environ.get("INDEX_VERSION", "1.0") ai_search_connection_name = os.environ.get("AI_SEARCH_CONNECTION_NAME", "my-ai-search-connection-name") diff --git a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py index d09bb48a72ad..4623807d024d 100644 --- a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py @@ -8,7 +8,7 @@ DESCRIPTION: This sample demonstrates how to directly interact with MCP (Model Context Protocol) tools using the low-level MCP client library to connect to the Foundry Project's MCP tools API: - {AZURE_AI_PROJECT_ENDPOINT}/mcp_tools?api-version=2025-05-15-preview + {PROJECT_ENDPOINT}/mcp_tools?api-version=2025-05-15-preview For agent-based MCP tool usage, see samples in samples/agents/tools/sample_agent_mcp.py and related files in that directory. @@ -29,7 +29,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv mcp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) IMAGE_GEN_DEPLOYMENT_NAME - The deployment name of the image generation model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -64,7 +64,7 @@ # Enable httpx logging to see HTTP requests at the same level logging.getLogger("httpx").setLevel(getattr(logging, log_level, logging.CRITICAL)) -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py index 6af7c9c90f3c..85f8e3e5aec9 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py @@ -24,7 +24,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -46,7 +46,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py index f8b9643f7547..bde8a22a1c3e 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py @@ -24,7 +24,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -50,7 +50,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py index 0d3cf82f4fbe..ff307a66cbb9 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py @@ -22,7 +22,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -43,7 +43,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py index 6999a6c154ca..fe12d1db07ee 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py @@ -23,7 +23,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -48,7 +48,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py index 94d1abdab835..cf116e910d08 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py @@ -20,7 +20,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -37,7 +37,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py index 730dd3a53534..23fc4782e98e 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py @@ -20,7 +20,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py index 918ae00569e5..bdf4936f8f6c 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. + 2) MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. 3) MODEL_ENDPOINT - Required. The Azure AI Model endpoint, as found in the overview page of your Microsoft Foundry project. Example: https://.services.ai.azure.com 4) MODEL_API_KEY - Required. The API key for your Azure AI Model. @@ -38,11 +38,11 @@ load_dotenv() endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ model_endpoint = os.environ["MODEL_ENDPOINT"] # Sample : https://.services.ai.azure.com model_api_key = os.environ["MODEL_API_KEY"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini +model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py index 261b0f9aaedc..54170b0d3492 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. + 2) MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. 3) MODEL_ENDPOINT - Required. The Azure AI Model endpoint, as found in the overview page of your Microsoft Foundry project. Example: https://.services.ai.azure.com 4) MODEL_API_KEY - Required. The API key for your Azure AI Model. @@ -43,11 +43,11 @@ async def sample_red_team_async() -> None: """Demonstrates how to perform Red Team operations using the AIProjectClient.""" endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ model_endpoint = os.environ["MODEL_ENDPOINT"] # Sample : https://.services.ai.azure.com model_api_key = os.environ["MODEL_API_KEY"] - model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini + model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py index 436237b03c46..062aef48577e 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -40,13 +40,13 @@ # [START responses] with project_client.get_openai_client() as openai_client: response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py index b68ab13eddd7..0501cc142cbe 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] async def main() -> None: @@ -43,13 +43,13 @@ async def main() -> None: project_client.get_openai_client() as openai_client, ): response = await openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = await openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py index 9c5d8b656bd7..39375d948e2d 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py @@ -19,9 +19,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,11 +34,11 @@ openai = OpenAI( api_key=get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default"), - base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) response = openai.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="How many feet are in a mile?", ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py index 8a2934ff7418..47b46923f9b3 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py @@ -19,9 +19,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,13 +42,13 @@ async def main() -> None: openai = AsyncOpenAI( api_key=get_bearer_token_provider(credential, "https://ai.azure.com/.default"), - base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) async with openai: response = await openai.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input="How many feet are in a mile?", ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py index 542d956b1cf0..1299f15d638c 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] def image_to_base64(image_path: str) -> str: @@ -71,6 +71,6 @@ def image_to_base64(image_path: str) -> str: ], } ], - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], ) print(f"Response output: {response.output_text}") diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py index 86f3b2d1fa47..8d7f60673c2b 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -43,7 +43,7 @@ ): with openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input=[ {"role": "user", "content": "Tell me about the capital city of France"}, ], diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py index 110bb0b1c4be..6508bd40deb4 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -42,7 +42,7 @@ ): with openai_client.responses.stream( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], input=[ {"role": "user", "content": "Tell me about the capital city of France"}, ], diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py index d1c75654083a..a781b8152edc 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,7 +42,7 @@ class CalendarEvent(BaseModel): participants: list[str] -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ class CalendarEvent(BaseModel): project_client.get_openai_client() as openai_client, ): response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["MODEL_DEPLOYMENT_NAME"], instructions=""" Extracts calendar event information from the input messages, and return it in the desired structured output format. diff --git a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py index 61f11436620b..b5ef4f48234a 100644 --- a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py +++ b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. """ @@ -28,7 +28,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py index 7554370401f4..4e98aff356bd 100644 --- a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py +++ b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your + 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py index d7aede04fb65..c8a6ebc74921 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py @@ -273,7 +273,7 @@ def _test_agent_creation_with_tracing_content_recording_enabled_impl(self, use_e with self.create_client(operation_group="tracing", **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") print(f"Using model deployment: {model}") agent_definition = PromptAgentDefinition( @@ -387,7 +387,7 @@ def _test_agent_creation_with_tracing_content_recording_disabled_impl(self, use_ with self.create_client(operation_group="agents", **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") agent_definition = PromptAgentDefinition( model=model, instructions="You are a helpful AI assistant. Always be polite and provide accurate information.", @@ -609,7 +609,7 @@ def _test_agent_with_structured_output_with_instructions_impl( operation_group = "tracing" if content_recording_enabled else "agents" with self.create_client(operation_group=operation_group, **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") test_schema = { "type": "object", @@ -797,7 +797,7 @@ def _test_agent_with_structured_output_without_instructions_impl( operation_group = "tracing" if content_recording_enabled else "agents" with self.create_client(operation_group=operation_group, **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") test_schema = { "type": "object", diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py index 96184005a350..80f20632fd98 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py @@ -75,7 +75,7 @@ async def _test_create_agent_with_tracing_content_recording_enabled_impl(self, u assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with project_client: agent_definition = PromptAgentDefinition( @@ -186,7 +186,7 @@ async def _test_agent_creation_with_tracing_content_recording_disabled_impl(self assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="agents", **kwargs) - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with project_client: agent_definition = PromptAgentDefinition( @@ -406,7 +406,7 @@ async def _test_agent_with_structured_output_with_instructions_impl( project_client = self.create_async_client(operation_group=operation_group, **kwargs) async with project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") test_schema = { "type": "object", @@ -591,7 +591,7 @@ async def _test_agent_with_structured_output_without_instructions_impl( project_client = self.create_async_client(operation_group=operation_group, **kwargs) async with project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") test_schema = { "type": "object", diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py index 7b8471717f2b..23a9116edd60 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py @@ -78,7 +78,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: openai_client = project_client.get_openai_client() # Get the model deployment name from test parameters - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_deployment_name = kwargs.get("model_deployment_name") return openai_client, model_deployment_name @@ -234,7 +234,7 @@ def _test_sync_non_streaming_with_content_recording_impl(self, use_events, **kwa with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -356,7 +356,7 @@ def _test_sync_non_streaming_without_content_recording_impl(self, use_events, ** with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -479,7 +479,7 @@ def _test_sync_streaming_with_content_recording_impl(self, use_events, **kwargs) with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -656,7 +656,7 @@ def test_sync_conversations_create(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -701,7 +701,7 @@ def test_sync_list_conversation_items_with_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -778,7 +778,7 @@ def test_sync_list_conversation_items_without_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -887,7 +887,7 @@ def _test_sync_non_streaming_without_conversation_impl(self, use_events, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") with project_client: # Get the OpenAI client from the project client @@ -994,7 +994,7 @@ def _test_sync_function_tool_with_content_recording_non_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -1253,7 +1253,7 @@ def _test_sync_function_tool_with_content_recording_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -1577,7 +1577,7 @@ def _test_sync_function_tool_without_content_recording_non_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -1813,7 +1813,7 @@ def _test_sync_function_tool_without_content_recording_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -2131,7 +2131,7 @@ def test_sync_function_tool_list_conversation_items_with_content_recording(self, with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -2281,7 +2281,7 @@ def test_sync_function_tool_list_conversation_items_without_content_recording(se with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -2429,7 +2429,7 @@ def test_sync_multiple_text_inputs_with_content_recording_non_streaming(self, ** with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2526,7 +2526,7 @@ def test_sync_multiple_text_inputs_with_content_recording_streaming(self, **kwar with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2631,7 +2631,7 @@ def test_sync_multiple_text_inputs_without_content_recording_non_streaming(self, with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2723,7 +2723,7 @@ def test_sync_multiple_text_inputs_without_content_recording_streaming(self, **k with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2824,7 +2824,7 @@ def _test_image_only_content_off_binary_off_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -2929,7 +2929,7 @@ def _test_image_only_content_off_binary_on_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3033,7 +3033,7 @@ def _test_image_only_content_on_binary_off_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3137,7 +3137,7 @@ def _test_image_only_content_on_binary_on_non_streaming_impl(self, use_events, * with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3245,7 +3245,7 @@ def _test_text_and_image_content_off_binary_off_non_streaming_impl(self, use_eve with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3354,7 +3354,7 @@ def _test_text_and_image_content_off_binary_on_non_streaming_impl(self, use_even with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3463,7 +3463,7 @@ def _test_text_and_image_content_on_binary_off_non_streaming_impl(self, use_even with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3571,7 +3571,7 @@ def _test_text_and_image_content_on_binary_on_non_streaming_impl(self, use_event with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3683,7 +3683,7 @@ def _test_image_only_content_off_binary_off_streaming_impl(self, use_events, **k with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3796,7 +3796,7 @@ def _test_image_only_content_off_binary_on_streaming_impl(self, use_events, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -3908,7 +3908,7 @@ def _test_image_only_content_on_binary_off_streaming_impl(self, use_events, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4020,7 +4020,7 @@ def _test_image_only_content_on_binary_on_streaming_impl(self, use_events, **kwa with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4136,7 +4136,7 @@ def _test_text_and_image_content_off_binary_off_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4253,7 +4253,7 @@ def _test_text_and_image_content_off_binary_on_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4370,7 +4370,7 @@ def _test_text_and_image_content_on_binary_off_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4486,7 +4486,7 @@ def _test_text_and_image_content_on_binary_on_streaming_impl(self, use_events, * with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4601,7 +4601,7 @@ def test_responses_stream_method_with_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4654,7 +4654,7 @@ def test_responses_stream_method_without_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = client.conversations.create() @@ -4709,7 +4709,7 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -4823,7 +4823,7 @@ def test_responses_stream_method_with_tools_without_content_recording(self, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -4960,7 +4960,7 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): assert True == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") openai_client = project_client.get_openai_client() # Create Teacher Agent @@ -5159,7 +5159,7 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): assert False == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") openai_client = project_client.get_openai_client() workflow_yaml = """ @@ -5276,7 +5276,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): assert True == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") openai_client = project_client.get_openai_client() # Create Teacher Agent @@ -5478,7 +5478,7 @@ def test_workflow_agent_streaming_without_content_recording(self, **kwargs): assert False == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") openai_client = project_client.get_openai_client() workflow_yaml = """ diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py index 8f2b18126533..490715fa0333 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py @@ -59,7 +59,7 @@ async def _test_async_non_streaming_with_content_recording_impl(self, use_events assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -165,7 +165,7 @@ async def _test_async_streaming_with_content_recording_impl(self, use_events, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -277,7 +277,7 @@ async def test_async_conversations_create(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -325,7 +325,7 @@ async def test_async_list_conversation_items_with_content_recording(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -417,7 +417,7 @@ async def _test_async_function_tool_with_content_recording_streaming_impl( async with project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -678,7 +678,7 @@ async def _test_async_function_tool_without_content_recording_streaming_impl( async with project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -924,7 +924,7 @@ async def test_async_multiple_text_inputs_with_content_recording_non_streaming(s async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -1021,7 +1021,7 @@ async def test_async_multiple_text_inputs_with_content_recording_streaming(self, async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -1126,7 +1126,7 @@ async def test_async_multiple_text_inputs_without_content_recording_non_streamin async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -1225,7 +1225,7 @@ async def test_async_image_only_content_off_binary_off_non_streaming(self, **kwa assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1297,7 +1297,7 @@ async def test_async_image_only_content_off_binary_on_non_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1368,7 +1368,7 @@ async def test_async_image_only_content_on_binary_off_non_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1439,7 +1439,7 @@ async def test_async_image_only_content_on_binary_on_non_streaming(self, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1514,7 +1514,7 @@ async def test_async_text_and_image_content_off_binary_off_non_streaming(self, * assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1590,7 +1590,7 @@ async def test_async_text_and_image_content_off_binary_on_non_streaming(self, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1665,7 +1665,7 @@ async def test_async_text_and_image_content_on_binary_off_non_streaming(self, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1740,7 +1740,7 @@ async def test_async_text_and_image_content_on_binary_on_non_streaming(self, **k assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1819,7 +1819,7 @@ async def test_async_image_only_content_off_binary_off_streaming(self, **kwargs) assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1899,7 +1899,7 @@ async def test_async_image_only_content_off_binary_on_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1978,7 +1978,7 @@ async def test_async_image_only_content_on_binary_off_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2057,7 +2057,7 @@ async def test_async_image_only_content_on_binary_on_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2140,7 +2140,7 @@ async def test_async_text_and_image_content_off_binary_off_streaming(self, **kwa assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2223,7 +2223,7 @@ async def test_async_text_and_image_content_off_binary_on_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2306,7 +2306,7 @@ async def test_async_text_and_image_content_on_binary_off_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2389,7 +2389,7 @@ async def test_async_text_and_image_content_on_binary_on_streaming(self, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2474,7 +2474,7 @@ async def test_async_multiple_text_inputs_without_content_recording_streaming(se async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -2582,7 +2582,7 @@ async def test_async_responses_stream_method_with_content_recording(self, **kwar async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = await client.conversations.create() @@ -2662,7 +2662,7 @@ async def test_async_responses_stream_method_without_content_recording(self, **k async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") conversation = await client.conversations.create() @@ -2750,7 +2750,7 @@ async def _test_async_responses_stream_method_with_tools_with_content_recording_ async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -2983,7 +2983,7 @@ async def _test_async_responses_stream_method_with_tools_without_content_recordi async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -3214,7 +3214,7 @@ async def test_async_workflow_agent_non_streaming_with_content_recording(self, * assert True == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: # Create a simple workflow agent @@ -3331,7 +3331,7 @@ async def test_async_workflow_agent_non_streaming_without_content_recording(self assert False == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: workflow_yaml = """ @@ -3454,7 +3454,7 @@ async def test_async_workflow_agent_streaming_with_content_recording(self, **kwa assert True == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: workflow_yaml = """ @@ -3575,7 +3575,7 @@ async def test_async_workflow_agent_streaming_without_content_recording(self, ** assert False == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: workflow_yaml = """ @@ -3708,7 +3708,7 @@ async def _test_async_prompt_agent_with_responses_non_streaming_impl( assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -3846,7 +3846,7 @@ async def _test_async_prompt_agent_with_responses_streaming_impl( assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") async with project_client: client = project_client.get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py index f68d4fdae952..7cb41a48cd3f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py @@ -59,7 +59,7 @@ def test_sync_browser_automation_non_streaming_with_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -194,7 +194,7 @@ def test_sync_browser_automation_non_streaming_without_content_recording(self, * assert not AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -320,7 +320,7 @@ def test_sync_browser_automation_streaming_with_content_recording(self, **kwargs self.setup_telemetry() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -448,7 +448,7 @@ def test_sync_browser_automation_streaming_without_content_recording(self, **kwa self.setup_telemetry() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py index 46918894c2fb..4b2454c510cb 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py @@ -62,7 +62,7 @@ async def test_async_browser_automation_non_streaming_with_content_recording(sel assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -193,7 +193,7 @@ async def test_async_browser_automation_non_streaming_without_content_recording( assert not AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -315,7 +315,7 @@ async def test_async_browser_automation_streaming_with_content_recording(self, * self.setup_telemetry() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -440,7 +440,7 @@ async def test_async_browser_automation_streaming_without_content_recording(self self.setup_telemetry() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py index 331d64b9aaa8..b23ea2b0e5e6 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py @@ -66,7 +66,7 @@ def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwar assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -254,7 +254,7 @@ def test_sync_code_interpreter_non_streaming_without_content_recording(self, **k assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -445,7 +445,7 @@ def test_sync_code_interpreter_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -636,7 +636,7 @@ def test_sync_code_interpreter_streaming_without_content_recording(self, **kwarg assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py index 7e5512b6fbbe..007b78ce5bd1 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py @@ -67,7 +67,7 @@ async def test_async_code_interpreter_non_streaming_with_content_recording(self, assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -254,7 +254,7 @@ async def test_async_code_interpreter_non_streaming_without_content_recording(se assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -445,7 +445,7 @@ async def test_async_code_interpreter_streaming_with_content_recording(self, **k assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -636,7 +636,7 @@ async def test_async_code_interpreter_streaming_without_content_recording(self, assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py index ca4f301212f3..76faa8c49ff4 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py @@ -53,7 +53,7 @@ def test_sync_file_search_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -262,7 +262,7 @@ def test_sync_file_search_non_streaming_without_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -469,7 +469,7 @@ def test_sync_file_search_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -674,7 +674,7 @@ def test_sync_file_search_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py index fd6c36261449..647d24530f54 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py @@ -54,7 +54,7 @@ async def test_async_file_search_non_streaming_with_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -263,7 +263,7 @@ async def test_async_file_search_non_streaming_without_content_recording(self, * assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -470,7 +470,7 @@ async def test_async_file_search_streaming_with_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -675,7 +675,7 @@ async def test_async_file_search_streaming_without_content_recording(self, **kwa assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py index 4c5c453a3e23..3ef49deac892 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py @@ -59,7 +59,7 @@ def _test_sync_mcp_non_streaming_with_content_recording_impl(self, use_events, * assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -389,7 +389,7 @@ def _test_sync_mcp_non_streaming_without_content_recording_impl(self, use_events assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -706,7 +706,7 @@ def _test_sync_mcp_streaming_with_content_recording_impl(self, use_events, **kwa assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -982,7 +982,7 @@ def _test_sync_mcp_streaming_without_content_recording_impl(self, use_events, ** assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py index d9e82e2951e8..c218a653295c 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py @@ -60,7 +60,7 @@ async def _test_async_mcp_non_streaming_with_content_recording_impl(self, use_ev assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -389,7 +389,7 @@ async def _test_async_mcp_non_streaming_without_content_recording_impl(self, use assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -708,7 +708,7 @@ async def _test_async_mcp_streaming_with_content_recording_impl(self, use_events assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -987,7 +987,7 @@ async def _test_async_mcp_streaming_without_content_recording_impl(self, use_eve assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py index a198327679c3..f75e8d9694e7 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py @@ -41,7 +41,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: openai_client = project_client.get_openai_client() # Get the model deployment name from test parameters - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_deployment_name = kwargs.get("model_deployment_name") return openai_client, model_deployment_name diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py index bec6cfa9f2be..492c49c9d7af 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py @@ -206,7 +206,7 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -371,7 +371,7 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -538,7 +538,7 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: @@ -706,7 +706,7 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py index e366e1ec3ef5..1ce2aa436e88 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py @@ -205,7 +205,7 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -366,7 +366,7 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -531,7 +531,7 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: @@ -697,7 +697,7 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py index 1cc36a6b0455..92fca677d439 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py @@ -48,7 +48,7 @@ def test_agent_responses_crud(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -158,7 +158,7 @@ def test_agent_responses_crud(self, **kwargs): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_agent_responses_with_structured_output(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py index b710851c366f..449dead94abc 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py @@ -23,7 +23,7 @@ class TestAgentResponsesCrudAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_responses_crud_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) @@ -129,7 +129,7 @@ async def test_agent_responses_crud_async(self, **kwargs): @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_responses_with_structured_output_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py index 10414b7a59d1..724baf8c022b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py @@ -39,7 +39,7 @@ def test_agents_crud(self, **kwargs): GET /agents/{agent_name}/versions/{agent_version} project_client.agents.get_version() """ print("\n") - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") project_client = self.create_client(operation_group="agents", **kwargs) first_agent_name = "MyAgent1" second_agent_name = "MyAgent2" diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py index e9776b7e6257..9853f973e098 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py @@ -24,7 +24,7 @@ async def test_agents_crud_async(self, **kwargs): It then gets, lists, and deletes them, validating at each step. It uses different ways of creating agents: strongly typed, dictionary, and IO[bytes]. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") project_client = self.create_async_client(operation_group="agents", **kwargs) first_agent_name = "MyAgent1" second_agent_name = "MyAgent2" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py index 3953bf1c76d2..e835e18936b3 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py @@ -40,7 +40,7 @@ def test_calculate_and_save(self, **kwargs): 2. Function Tool: Saves the computed result """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -100,7 +100,7 @@ def test_generate_data_and_report(self, **kwargs): 2. Function Tool: Creates a report with the computed statistics """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py index 16f2c2c1ba41..a6da388ec0b9 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py @@ -39,7 +39,7 @@ def test_find_and_analyze_data(self, **kwargs): 2. Code Interpreter: Agent calculates the average of those numbers """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -121,7 +121,7 @@ def test_analyze_code_file(self, **kwargs): 2. Code Interpreter: Agent executes the code and returns the computed result """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py index f67e95c020a8..b127a14bc89d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py @@ -32,7 +32,7 @@ def test_data_analysis_workflow(self, **kwargs): Test data analysis workflow: upload data, search, save results. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -163,7 +163,7 @@ def test_empty_vector_store_handling(self, **kwargs): Test how agent handles empty vector store (no files uploaded). """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -242,7 +242,7 @@ def test_python_code_file_search(self, **kwargs): 2. Function Tool: Agent saves the code review findings """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -372,7 +372,7 @@ def test_multi_turn_search_and_save_workflow(self, **kwargs): - Context retention across searches and function calls """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py index 61d572fa0a37..3d2d12353dfb 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py @@ -43,7 +43,7 @@ def test_complete_analysis_workflow(self, **kwargs): 3. Function Tool: Agent saves the computed results """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py index 1ae26a32a1a4..1bc2d553caa2 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py @@ -39,7 +39,7 @@ def test_file_search_and_function_with_conversation(self, **kwargs): - Verifying conversation state preserves all tool interactions """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py index 02d341051ec8..d2a9add36b0a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py @@ -81,7 +81,7 @@ def test_agent_ai_search_question_answering(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Get AI Search connection and index from environment ai_search_connection_id = kwargs.get("ai_search_project_connection_id") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py index 5bc67d9a2833..f7b55542a649 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py @@ -148,7 +148,7 @@ async def test_agent_ai_search_question_answering_async_parallel(self, **kwargs) DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py index 78ec18081aa0..300914195e05 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py @@ -45,7 +45,7 @@ def test_agent_bing_grounding(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Note: This test requires bing_project_connection_id environment variable # to be set with a valid Bing connection ID from the project @@ -145,7 +145,7 @@ def test_agent_bing_grounding_multiple_queries(self, **kwargs): Bing grounding and provide accurate responses with citations. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") bing_connection_id = kwargs.get("bing_project_connection_id") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py index 7b5d1ea27680..f6531d9a1c6b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py @@ -41,7 +41,7 @@ def test_agent_code_interpreter_simple_math(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") agent_name = "code-interpreter-simple-agent" with ( @@ -125,7 +125,7 @@ def test_agent_code_interpreter_file_generation(self, **kwargs): DELETE /files/{file_id} openai_client.files.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py index d38d15b2bd0e..84f722173fa7 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py @@ -28,7 +28,7 @@ async def test_agent_code_interpreter_simple_math_async(self, **kwargs): without any file uploads or downloads - just pure code execution. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") agent_name = "code-interpreter-simple-agent-async" async with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py index e7408afe97fa..c398c3d6a1c4 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py @@ -45,7 +45,7 @@ def test_agent_file_search(self, **kwargs): DELETE /vector_stores/{id} openai_client.vector_stores.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -203,7 +203,7 @@ def test_agent_file_search_multi_turn_conversation(self, **kwargs): while using File Search to answer follow-up questions. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py index e3d96f5a4733..420b43ffca2b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py @@ -20,7 +20,7 @@ class TestAgentFileSearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_file_search_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, @@ -106,7 +106,7 @@ async def test_agent_file_search_multi_turn_conversation_async(self, **kwargs): while using File Search to answer follow-up questions. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py index e97814456771..861f3602d450 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py @@ -42,7 +42,7 @@ def test_agent_file_search_stream(self, **kwargs): DELETE /vector_stores/{id} openai_client.vector_stores.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py index fb4e627df2de..9bcad6d77fc2 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py @@ -19,7 +19,7 @@ class TestAgentFileSearchStreamAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_file_search_stream_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py index 264bf97ebf73..51216e5c6300 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py @@ -41,7 +41,7 @@ def test_agent_function_tool(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") agent_name = "function-tool-agent" with ( @@ -172,7 +172,7 @@ def test_agent_function_tool_multi_turn_with_multiple_calls(self, **kwargs): - Ability to use previous function results in subsequent queries """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -381,7 +381,7 @@ def test_agent_function_tool_context_dependent_followup(self, **kwargs): remembering parameters from the first query. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py index f4388b1ccfe9..92b806f0935c 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py @@ -28,7 +28,7 @@ async def test_agent_function_tool_async(self, **kwargs): 3. Receive function results and incorporate them into responses """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") agent_name = "function-tool-agent-async" # Setup @@ -160,7 +160,7 @@ async def test_agent_function_tool_multi_turn_with_multiple_calls_async(self, ** - Ability to use previous function results in subsequent queries """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) @@ -370,7 +370,7 @@ async def test_agent_function_tool_context_dependent_followup_async(self, **kwar remembering parameters from the first query. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") # Setup async with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py index c0c515839aaf..be27c5685b53 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py @@ -41,7 +41,7 @@ def test_agent_image_generation(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") image_model = kwargs.get("image_generation_model_deployment_name") agent_name = "image-gen-agent" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py index a4775afb16b9..4450f75fd103 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py @@ -21,7 +21,7 @@ class TestAgentImageGenerationAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_image_generation_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") image_model = kwargs.get("image_generation_model_deployment_name") agent_name = "image-gen-agent" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py index 5723478f7569..243b8a62c71e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py @@ -48,7 +48,7 @@ def test_agent_mcp_basic(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -179,7 +179,7 @@ def test_agent_mcp_with_project_connection(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py index 36a951e79183..dfc0df69188b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py @@ -21,7 +21,7 @@ class TestAgentMCPAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_mcp_basic_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py index 3a1bc4e44d0d..2cc6b1023295 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py @@ -54,7 +54,7 @@ def test_agent_memory_search(self, **kwargs): DELETE /memory_stores/{memory_store_name} project_client.beta.memory_stores.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") embedding_model = kwargs.get("memory_store_embedding_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py index dc6b69d22354..e1a888787053 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py @@ -29,7 +29,7 @@ class TestAgentMemorySearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_memory_search_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") embedding_model = kwargs.get("memory_store_embedding_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py index de8b85f19723..6905e4f27b6d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py @@ -51,7 +51,7 @@ def test_agent_openapi(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py index 1b3e87ef063a..a2da6989196e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py @@ -31,7 +31,7 @@ class TestAgentOpenApiAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_openapi_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py index 00fdf016e79d..110241a76862 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py @@ -40,7 +40,7 @@ def test_function_tool_with_conversation(self, **kwargs): - Using conversation_id parameter """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -201,7 +201,7 @@ def test_file_search_with_conversation(self, **kwargs): - Conversation context retention """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -318,7 +318,7 @@ def test_code_interpreter_with_conversation(self, **kwargs): - Variables/state persistence across turns """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -403,7 +403,7 @@ def test_code_interpreter_with_file_in_conversation(self, **kwargs): - Server-side code execution with file access and chart generation """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") import os with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py index 9a8f616e9d7f..d242c7bb6c17 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py @@ -38,7 +38,7 @@ def test_agent_web_search(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py index e11732ca4cac..2a9bc17b5ee9 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py @@ -18,7 +18,7 @@ class TestAgentWebSearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_web_search_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py index 9e790b1f37c7..c29bbf42edcc 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py @@ -137,7 +137,7 @@ def test_datasets_upload_file(self, **kwargs): @recorded_by_proxy def test_datasets_upload_folder(self, **kwargs): - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("project_endpoint") print("\n=====> Endpoint:", endpoint) dataset_name = self.test_datasets_params["dataset_name_2"] diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py index 724b6318b938..678bd1bedae2 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py @@ -138,7 +138,7 @@ async def test_datasets_upload_file(self, **kwargs): @recorded_by_proxy_async async def test_datasets_upload_folder_async(self, **kwargs): - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("project_endpoint") print("\n=====> Endpoint:", endpoint) dataset_name = self.test_datasets_params["dataset_name_4"] diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py index 53132a89a396..a4dc057d9765 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py @@ -18,8 +18,8 @@ class TestDeployments(TestBase): def test_deployments(self, **kwargs): model_publisher = "OpenAI" - model_name = kwargs.get("azure_ai_model_deployment_name") - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_name = kwargs.get("model_deployment_name") + model_deployment_name = kwargs.get("model_deployment_name") with self.create_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py index 06f229c1e15b..dba5495b0038 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py @@ -18,8 +18,8 @@ class TestDeploymentsAsync(TestBase): async def test_deployments_async(self, **kwargs): model_publisher = "OpenAI" - model_name = kwargs.get("azure_ai_model_deployment_name") - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_name = kwargs.get("model_deployment_name") + model_deployment_name = kwargs.get("model_deployment_name") async with self.create_async_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index d8e28452557f..51ed46e70849 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -334,11 +334,11 @@ def _test_deploy_and_infer_helper( subscription_id = kwargs.get("azure_subscription_id") resource_group = kwargs.get("azure_resource_group") - project_endpoint = kwargs.get("azure_ai_project_endpoint") + project_endpoint = kwargs.get("project_endpoint") if not all([subscription_id, resource_group, project_endpoint]): pytest.skip( - f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, azure_ai_project_endpoint) - skipping {test_prefix} deploy and infer test" + f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, project_endpoint) - skipping {test_prefix} deploy and infer test" ) account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index be0cc2de95dc..117c06684914 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -345,11 +345,11 @@ async def _test_deploy_and_infer_helper_async( subscription_id = kwargs.get("azure_subscription_id") resource_group = kwargs.get("azure_resource_group") - project_endpoint = kwargs.get("azure_ai_project_endpoint") + project_endpoint = kwargs.get("project_endpoint") if not all([subscription_id, resource_group, project_endpoint]): pytest.skip( - f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, azure_ai_project_endpoint) - skipping {test_prefix} deploy and infer test" + f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, project_endpoint) - skipping {test_prefix} deploy and infer test" ) account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py index 5165f37ddb03..77f354335d4a 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py @@ -57,7 +57,7 @@ def test_responses(self, **kwargs): ------+---------------------------------------------+----------------------------------- POST /openai/responses client.responses.create() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") client = self.create_client(operation_group="agents", **kwargs).get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py index bf7252962dad..187bbde4a259 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py @@ -45,7 +45,7 @@ class TestResponsesAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.HTTPX) async def test_responses_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("model_deployment_name") client = self.create_async_client(operation_group="agents", **kwargs).get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/samples/README.md b/sdk/ai/azure-ai-projects/tests/samples/README.md index 3296cb7c58ea..4f2547678cbe 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/README.md +++ b/sdk/ai/azure-ai-projects/tests/samples/README.md @@ -67,7 +67,7 @@ class TestSamples(AzureRecordedTestCase): executor.execute() executor.validate_print_calls_by_llm( instructions=agent_tools_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], + project_endpoint=kwargs["project_endpoint"], ) ``` @@ -106,7 +106,7 @@ class TestSamplesAsync(AzureRecordedTestCase): await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], + project_endpoint=kwargs["project_endpoint"], ) ``` @@ -122,8 +122,8 @@ from devtools_testutils import EnvironmentVariableLoader servicePreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="gpt-4o", + project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + model_deployment_name="gpt-4o", # add other sanitized vars here ) ``` @@ -154,8 +154,8 @@ If you need to remap the values provided by your fixtures to the environment-var ```python env_vars = { - "AZURE_AI_PROJECT_ENDPOINT": kwargs["TEST_AZURE_AI_PROJECT_ENDPOINT"], - "AZURE_AI_MODEL_DEPLOYMENT_NAME": kwargs["TEST_AZURE_AI_MODEL_DEPLOYMENT_NAME"], + "PROJECT_ENDPOINT": kwargs["TEST_AZURE_AI_PROJECT_ENDPOINT"], + "MODEL_DEPLOYMENT_NAME": kwargs["TEST_AZURE_AI_MODEL_DEPLOYMENT_NAME"], } executor = SyncSampleExecutor(self, sample_path, env_vars=env_vars, **kwargs) ``` diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 2bd4594c843d..0dac34211592 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -62,8 +62,8 @@ def test_agent_tools_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -86,8 +86,8 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=memories_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -106,8 +106,8 @@ def test_agents_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=agents_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -128,8 +128,8 @@ def test_connections_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -148,8 +148,8 @@ def test_files_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -168,8 +168,8 @@ def test_deployments_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -188,8 +188,8 @@ def test_datasets_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -211,6 +211,6 @@ def test_finetuning_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=fine_tuning_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py index 8eccef50195f..fc6fecc417d6 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py @@ -50,8 +50,8 @@ async def test_agent_tools_samples_async(self, sample_path: str, **kwargs) -> No await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -75,8 +75,8 @@ async def test_memory_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=memories_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -95,8 +95,8 @@ async def test_agents_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agents_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -117,8 +117,8 @@ async def test_connections_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -139,8 +139,8 @@ async def test_files_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -159,8 +159,8 @@ async def test_deployments_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) @pytest.mark.parametrize( @@ -184,6 +184,6 @@ async def test_datasets_samples(self, sample_path: str, **kwargs) -> None: # Proxy server probably not able to parse the captured print content await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index bcea91df0eb5..9390fdf2d895 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -19,8 +19,8 @@ evaluationsPreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="sanitized-model-deployment-name", + project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + model_deployment_name="sanitized-model-deployment-name", azure_ai_agent_name="sanitized-agent-name", ) @@ -184,8 +184,8 @@ def test_evaluation_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) # To run this test with a specific sample, use: @@ -216,8 +216,8 @@ def test_agentic_evaluator_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) # To run this test, use: @@ -247,6 +247,6 @@ def test_generic_agentic_evaluator_sample(self, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["project_endpoint"], + model=kwargs["model_deployment_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index a0deb29a2cfd..afcbe80e2317 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -41,8 +41,8 @@ servicePreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="sanitized-model-deployment-name", + project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + model_deployment_name="sanitized-model-deployment-name", image_generation_model_deployment_name="sanitized-gpt-image", container_app_resource_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/00000/providers/Microsoft.App/containerApps/00000", container_ingress_subdomain_suffix="00000", @@ -77,8 +77,8 @@ fineTuningServicePreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="sanitized-model-deployment-name", + project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + model_deployment_name="sanitized-model-deployment-name", azure_ai_projects_azure_subscription_id="00000000-0000-0000-0000-000000000000", azure_ai_projects_azure_resource_group="sanitized-resource-group", azure_ai_projects_azure_aoai_account="sanitized-aoai-account", @@ -299,7 +299,7 @@ def open_with_lf( # helper function: create projects client using environment variables def create_client(self, *, operation_group: Optional[str] = None, **kwargs) -> AIProjectClient: # fetch environment variables - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("project_endpoint") credential = self.get_credential(AIProjectClient, is_async=False) allow_preview = kwargs.pop("allow_preview", operation_group in {"agents", "tracing"}) @@ -317,7 +317,7 @@ def create_client(self, *, operation_group: Optional[str] = None, **kwargs) -> A # helper function: create async projects client using environment variables def create_async_client(self, *, operation_group: Optional[str] = None, **kwargs) -> AsyncAIProjectClient: # fetch environment variables - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("project_endpoint") credential = self.get_credential(AsyncAIProjectClient, is_async=True) allow_preview = kwargs.pop("allow_preview", operation_group in {"agents", "tracing"}) From 67a8d7949111c946492f4b543b545ede95c0f202 Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Mon, 9 Mar 2026 20:28:30 -0700 Subject: [PATCH 07/36] Revert "Rename env varrs name" This reverts commit 86d53c53e8ffa1fac49c9f7388ee1b3f9e9f321f. --- sdk/ai/azure-ai-projects/.env.template | 4 +- sdk/ai/azure-ai-projects/CHANGELOG.md | 4 +- sdk/ai/azure-ai-projects/README.md | 18 ++-- .../samples/agents/sample_agent_basic.py | 8 +- .../agents/sample_agent_basic_async.py | 8 +- .../agents/sample_agent_retrieve_basic.py | 8 +- .../sample_agent_retrieve_basic_async.py | 8 +- .../agents/sample_agent_stream_events.py | 8 +- .../agents/sample_agent_structured_output.py | 8 +- .../sample_agent_structured_output_async.py | 8 +- .../agents/sample_workflow_multi_agent.py | 10 +-- .../sample_workflow_multi_agent_async.py | 10 +-- ..._agent_basic_with_azure_monitor_tracing.py | 8 +- ...sample_agent_basic_with_console_tracing.py | 8 +- ..._with_console_tracing_custom_attributes.py | 8 +- .../agents/tools/sample_agent_ai_search.py | 8 +- .../tools/sample_agent_azure_function.py | 8 +- .../tools/sample_agent_bing_custom_search.py | 8 +- .../tools/sample_agent_bing_grounding.py | 8 +- .../tools/sample_agent_browser_automation.py | 8 +- .../tools/sample_agent_code_interpreter.py | 8 +- .../sample_agent_code_interpreter_async.py | 8 +- ...ample_agent_code_interpreter_with_files.py | 8 +- ...agent_code_interpreter_with_files_async.py | 8 +- .../agents/tools/sample_agent_computer_use.py | 4 +- .../tools/sample_agent_computer_use_async.py | 4 +- .../agents/tools/sample_agent_fabric.py | 8 +- .../agents/tools/sample_agent_file_search.py | 8 +- .../sample_agent_file_search_in_stream.py | 8 +- ...ample_agent_file_search_in_stream_async.py | 8 +- .../tools/sample_agent_function_tool.py | 8 +- .../tools/sample_agent_function_tool_async.py | 8 +- .../tools/sample_agent_image_generation.py | 8 +- .../sample_agent_image_generation_async.py | 8 +- .../samples/agents/tools/sample_agent_mcp.py | 8 +- .../agents/tools/sample_agent_mcp_async.py | 8 +- ...ample_agent_mcp_with_project_connection.py | 8 +- ...agent_mcp_with_project_connection_async.py | 8 +- .../tools/sample_agent_memory_search.py | 8 +- .../tools/sample_agent_memory_search_async.py | 8 +- .../agents/tools/sample_agent_openapi.py | 8 +- ...e_agent_openapi_with_project_connection.py | 8 +- .../agents/tools/sample_agent_sharepoint.py | 8 +- .../agents/tools/sample_agent_to_agent.py | 8 +- .../agents/tools/sample_agent_web_search.py | 8 +- .../tools/sample_agent_web_search_preview.py | 8 +- ...ple_agent_web_search_with_custom_search.py | 8 +- .../samples/connections/sample_connections.py | 4 +- .../connections/sample_connections_async.py | 4 +- .../samples/datasets/sample_datasets.py | 4 +- .../samples/datasets/sample_datasets_async.py | 4 +- .../datasets/sample_datasets_download.py | 4 +- .../samples/deployments/sample_deployments.py | 8 +- .../deployments/sample_deployments_async.py | 8 +- .../samples/evaluations/README.md | 8 +- .../agentic_evaluators/sample_coherence.py | 8 +- .../agentic_evaluators/sample_fluency.py | 8 +- .../agent_utils.py | 2 +- .../sample_generic_agentic_evaluator.py | 6 +- .../agentic_evaluators/sample_groundedness.py | 8 +- .../sample_intent_resolution.py | 8 +- .../agentic_evaluators/sample_relevance.py | 8 +- .../sample_response_completeness.py | 8 +- .../sample_task_adherence.py | 8 +- .../sample_task_completion.py | 8 +- .../sample_task_navigation_efficiency.py | 4 +- .../sample_tool_call_accuracy.py | 8 +- .../sample_tool_call_success.py | 8 +- .../sample_tool_input_accuracy.py | 8 +- .../sample_tool_output_utilization.py | 8 +- .../sample_tool_selection.py | 8 +- .../evaluations/sample_agent_evaluation.py | 8 +- .../sample_agent_response_evaluation.py | 8 +- ..._response_evaluation_with_function_tool.py | 8 +- .../sample_continuous_evaluation_rule.py | 8 +- .../evaluations/sample_eval_catalog.py | 4 +- ...mple_eval_catalog_code_based_evaluators.py | 8 +- ...le_eval_catalog_prompt_based_evaluators.py | 8 +- .../sample_evaluation_cluster_insight.py | 10 +-- .../sample_evaluation_compare_insight.py | 8 +- .../sample_evaluations_ai_assisted.py | 8 +- ...ple_evaluations_builtin_with_dataset_id.py | 8 +- ...le_evaluations_builtin_with_inline_data.py | 8 +- ...valuations_builtin_with_inline_data_oai.py | 8 +- .../sample_evaluations_builtin_with_traces.py | 8 +- .../evaluations/sample_evaluations_graders.py | 8 +- ...aluations_score_model_grader_with_image.py | 8 +- .../evaluations/sample_model_evaluation.py | 8 +- .../evaluations/sample_redteam_evaluations.py | 6 +- .../sample_scheduled_evaluations.py | 12 +-- .../samples/files/sample_files.py | 4 +- .../samples/files/sample_files_async.py | 4 +- .../finetuning/sample_finetuning_dpo_job.py | 4 +- .../sample_finetuning_dpo_job_async.py | 4 +- ...le_finetuning_oss_models_supervised_job.py | 4 +- ...etuning_oss_models_supervised_job_async.py | 4 +- .../sample_finetuning_reinforcement_job.py | 4 +- ...mple_finetuning_reinforcement_job_async.py | 4 +- .../sample_finetuning_supervised_job.py | 4 +- .../sample_finetuning_supervised_job_async.py | 4 +- .../samples/indexes/sample_indexes.py | 4 +- .../samples/indexes/sample_indexes_async.py | 4 +- .../mcp_client/sample_mcp_tool_async.py | 6 +- .../memories/sample_memory_advanced.py | 4 +- .../memories/sample_memory_advanced_async.py | 4 +- .../samples/memories/sample_memory_basic.py | 4 +- .../memories/sample_memory_basic_async.py | 4 +- .../samples/memories/sample_memory_crud.py | 4 +- .../memories/sample_memory_crud_async.py | 4 +- .../samples/red_team/sample_red_team.py | 8 +- .../samples/red_team/sample_red_team_async.py | 8 +- .../responses/sample_responses_basic.py | 10 +-- .../responses/sample_responses_basic_async.py | 10 +-- ...responses_basic_without_aiprojectclient.py | 8 +- ...ses_basic_without_aiprojectclient_async.py | 8 +- .../responses/sample_responses_image_input.py | 8 +- .../sample_responses_stream_events.py | 8 +- .../sample_responses_stream_manager.py | 8 +- .../sample_responses_structured_output.py | 8 +- .../samples/telemetry/sample_telemetry.py | 4 +- .../telemetry/sample_telemetry_async.py | 4 +- .../telemetry/test_ai_agents_instrumentor.py | 8 +- .../test_ai_agents_instrumentor_async.py | 8 +- .../telemetry/test_responses_instrumentor.py | 84 +++++++++---------- .../test_responses_instrumentor_async.py | 72 ++++++++-------- ...sponses_instrumentor_browser_automation.py | 8 +- ...s_instrumentor_browser_automation_async.py | 8 +- ...responses_instrumentor_code_interpreter.py | 8 +- ...ses_instrumentor_code_interpreter_async.py | 8 +- ...test_responses_instrumentor_file_search.py | 8 +- ...esponses_instrumentor_file_search_async.py | 8 +- .../test_responses_instrumentor_mcp.py | 8 +- .../test_responses_instrumentor_mcp_async.py | 8 +- .../test_responses_instrumentor_metrics.py | 2 +- .../test_responses_instrumentor_workflow.py | 8 +- ...t_responses_instrumentor_workflow_async.py | 8 +- .../tests/agents/test_agent_responses_crud.py | 4 +- .../agents/test_agent_responses_crud_async.py | 4 +- .../tests/agents/test_agents_crud.py | 2 +- .../tests/agents/test_agents_crud_async.py | 2 +- ...est_agent_code_interpreter_and_function.py | 4 +- ..._agent_file_search_and_code_interpreter.py | 4 +- .../test_agent_file_search_and_function.py | 8 +- ...t_file_search_code_interpreter_function.py | 2 +- .../test_multitool_with_conversations.py | 2 +- .../agents/tools/test_agent_ai_search.py | 2 +- .../tools/test_agent_ai_search_async.py | 2 +- .../agents/tools/test_agent_bing_grounding.py | 4 +- .../tools/test_agent_code_interpreter.py | 4 +- .../test_agent_code_interpreter_async.py | 2 +- .../agents/tools/test_agent_file_search.py | 4 +- .../tools/test_agent_file_search_async.py | 4 +- .../tools/test_agent_file_search_stream.py | 2 +- .../test_agent_file_search_stream_async.py | 2 +- .../agents/tools/test_agent_function_tool.py | 6 +- .../tools/test_agent_function_tool_async.py | 6 +- .../tools/test_agent_image_generation.py | 2 +- .../test_agent_image_generation_async.py | 2 +- .../tests/agents/tools/test_agent_mcp.py | 4 +- .../agents/tools/test_agent_mcp_async.py | 2 +- .../agents/tools/test_agent_memory_search.py | 2 +- .../tools/test_agent_memory_search_async.py | 2 +- .../tests/agents/tools/test_agent_openapi.py | 2 +- .../agents/tools/test_agent_openapi_async.py | 2 +- .../test_agent_tools_with_conversations.py | 8 +- .../agents/tools/test_agent_web_search.py | 2 +- .../tools/test_agent_web_search_async.py | 2 +- .../tests/datasets/test_datasets.py | 2 +- .../tests/datasets/test_datasets_async.py | 2 +- .../tests/deployments/test_deployments.py | 4 +- .../deployments/test_deployments_async.py | 4 +- .../tests/finetuning/test_finetuning.py | 4 +- .../tests/finetuning/test_finetuning_async.py | 4 +- .../tests/responses/test_responses.py | 2 +- .../tests/responses/test_responses_async.py | 2 +- .../azure-ai-projects/tests/samples/README.md | 12 +-- .../tests/samples/test_samples.py | 32 +++---- .../tests/samples/test_samples_async.py | 28 +++---- .../tests/samples/test_samples_evaluations.py | 16 ++-- sdk/ai/azure-ai-projects/tests/test_base.py | 12 +-- 180 files changed, 666 insertions(+), 668 deletions(-) diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index 89e81956f322..df66f2a71199 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -20,8 +20,8 @@ AZURE_AI_PROJECTS_CONSOLE_LOGGING= # Project endpoint has the format: # `https://.services.ai.azure.com/api/projects/` -PROJECT_ENDPOINT= -MODEL_DEPLOYMENT_NAME= +AZURE_AI_PROJECT_ENDPOINT= +AZURE_AI_MODEL_DEPLOYMENT_NAME= AZURE_AI_AGENT_NAME= CONVERSATION_ID= CONNECTION_NAME= diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 80b7bcd0be13..34c72e0a6fa5 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -20,9 +20,7 @@ ### Other Changes -* Replace environment variable name, `AZURE_AI_PROJECT_ENDPOINT` TO `PROJECT_ENDPOINT`. -* Replace environment variable name, `AZURE_AI_MODEL_DEPLOYMENT_NAME` TO `MODEL_DEPLOYMENT_NAME`. - +* Placeholder ## 2.0.0 (2026-03-06) diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index a9b17e078b94..8c741a297add 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -54,7 +54,7 @@ To report an issue with the client library, or request additional features, plea * Python 3.9 or later. * An [Azure subscription][azure_sub]. * A [project in Microsoft Foundry](https://learn.microsoft.com/azure/foundry/how-to/create-projects). -* A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `PROJECT_ENDPOINT` was defined to hold this value. +* A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `AZURE_AI_PROJECT_ENDPOINT` was defined to hold this value. * An Entra ID token for authentication. Your application needs an object that implements the [TokenCredential](https://learn.microsoft.com/python/api/azure-core/azure.core.credentials.tokencredential) interface. Code samples here use [DefaultAzureCredential](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential). To get that working, you will need: * An appropriate role assignment. See [Role-based access control in Microsoft Foundry portal](https://learn.microsoft.com/azure/foundry/concepts/rbac-foundry). Role assignment can be done via the "Access Control (IAM)" tab of your Azure AI Project resource in the Azure portal. * [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed. @@ -87,7 +87,7 @@ from azure.identity import DefaultAzureCredential with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, ): ``` @@ -107,7 +107,7 @@ from azure.identity.aio import DefaultAzureCredential async with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, ): ``` @@ -117,20 +117,20 @@ async with ( Your Microsoft Foundry project may have one or more AI models deployed. These could be OpenAI models, Microsoft models, or models from other providers. Use the code below to get an authenticated [OpenAI](https://github.com/openai/openai-python?tab=readme-ov-file#usage) client from the [openai](https://pypi.org/project/openai/) package, and execute an example multi-turn "Responses" calls. -The code below assumes the environment variable `MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). +The code below assumes the environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). ```python with project_client.get_openai_client() as openai_client: response = openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) @@ -145,7 +145,7 @@ See the "responses" folder in the [package samples][samples] for additional samp The `.agents` property on the `AIProjectClient` gives you access to all Agent operations. Agents use an extension of the OpenAI Responses protocol, so you will need to get an `OpenAI` client to do Agent operations, as shown in the example below. -The code below assumes environment variable `MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). +The code below assumes environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). See the "agents" folder in the [package samples][samples] for an extensive set of samples, including streaming, tool usage and memory store usage. @@ -156,7 +156,7 @@ with project_client.get_openai_client() as openai_client: agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) @@ -1357,7 +1357,7 @@ By default logs redact the values of URL query strings, the values of some HTTP ```python project_client = AIProjectClient( credential=DefaultAzureCredential(), - endpoint=os.environ["PROJECT_ENDPOINT"], + endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], logging_enable=True ) ``` diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py index a494cd33cd1e..1b57d0bcd29c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py index c4ddae1518c3..69404d31ae35 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main() -> None: @@ -48,7 +48,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions.", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py index 89d6192b6c7b..876dc8daebcd 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py @@ -22,9 +22,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -36,8 +36,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model = os.environ["MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py index 89320902dd05..8baa7034c139 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py @@ -22,9 +22,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -37,8 +37,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model = os.environ["MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py index cfb11d16d92b..5ab97c36b587 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py index 83f7e7803384..dfeb5c961720 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py @@ -24,9 +24,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv pydantic Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -51,7 +51,7 @@ class CalendarEvent(BaseModel): participants: list[str] -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -62,7 +62,7 @@ class CalendarEvent(BaseModel): agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], text=PromptAgentDefinitionTextOptions( format=TextResponseFormatJsonSchema(name="CalendarEvent", schema=CalendarEvent.model_json_schema()) ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py index a21d54e8426d..12bdaf31231a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py @@ -24,9 +24,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp pydantic Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] class CalendarEvent(BaseModel): @@ -63,7 +63,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], text=PromptAgentDefinitionTextOptions( format=TextResponseFormatJsonSchema(name="CalendarEvent", schema=CalendarEvent.model_json_schema()) ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py index 5def14deed3e..b8674dc5c146 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ teacher_agent = project_client.agents.create_version( agent_name="teacher-agent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", @@ -58,7 +58,7 @@ student_agent = project_client.agents.create_version( agent_name="student-agent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py index 160e059dbd39..b565a04be074 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -36,7 +36,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main(): @@ -50,7 +50,7 @@ async def main(): teacher_agent = await project_client.agents.create_version( agent_name="teacher-agent-async", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", @@ -61,7 +61,7 @@ async def main(): student_agent = await project_client.agents.create_version( agent_name="student-agent-async", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py index cdf10921d9a0..0f318459182f 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-monitor-opentelemetry Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -46,7 +46,7 @@ with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, ): # [START setup_azure_monitor_tracing] # Enable Azure Monitor tracing @@ -62,7 +62,7 @@ # [END create_span_for_scenario] with project_client.get_openai_client() as openai_client: agent_definition = PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py index 5495550a2131..e5aa0582e9a6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv opentelemetry-sdk azure-core-tracing-opentelemetry Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -90,11 +90,11 @@ def display_conversation_item(item: Any) -> None: # [END create_span_for_scenario] with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, project_client.get_openai_client() as openai_client, ): agent_definition = PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py index c35bc71b8823..64786e3a00e0 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv opentelemetry-sdk azure-core-tracing-opentelemetry Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] # Define the custom span processor that is used for adding the custom @@ -94,7 +94,7 @@ def on_end(self, span: ReadableSpan): ): agent_definition = PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py index e0e5cc4267ea..6f082269687a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AI_SEARCH_PROJECT_CONNECTION_ID - The AI Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -66,7 +66,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a helpful assistant. You must always provide citations for answers using the tool and render them as: `\u3010message_idx:search_idx\u2020source\u3011`.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py index d6f5aed57571..242ee742229b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0b1" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) STORAGE_INPUT_QUEUE_NAME - The name of the Azure Storage Queue to use for input and output in the Azure Function tool. 4) STORAGE_OUTPUT_QUEUE_NAME - The name of the Azure Storage Queue to use for output in the Azure Function tool. @@ -44,7 +44,7 @@ agent = None -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -82,7 +82,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py index 43c3246fb6f9..1ebf4c6d213b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py @@ -27,9 +27,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID - The Bing Custom Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -50,7 +50,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -74,7 +74,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a helpful agent that can use Bing Custom Search tools to assist users. Use the available Bing Custom Search tools to answer questions and perform tasks.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py index 7ca03afa983a..22fb479f0109 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py @@ -35,9 +35,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_PROJECT_CONNECTION_ID - The Bing project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. """ @@ -55,7 +55,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -76,7 +76,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py index 38129dea7b78..aa660411a6cc 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BROWSER_AUTOMATION_PROJECT_CONNECTION_ID - The browser automation project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are an Agent helping with browser automation tasks. You can answer questions, provide information, and assist with various tasks related to web browsing using the Browser Automation tool available to you.""", diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py index 72f9d84a4aa9..6007468f8439 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -47,7 +47,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py index de936fcfff6b..edde775f47aa 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main() -> None: @@ -46,7 +46,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[CodeInterpreterTool()], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py index 972d9ad84447..3cb511cfcc3b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -57,7 +57,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py index e275ab430db7..69ddd048bcc8 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main() -> None: @@ -59,7 +59,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=[file.id]))], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py index ec91666a1fa2..3b1ec849257c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py @@ -23,7 +23,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) (Optional) COMPUTER_USE_MODEL_DEPLOYMENT_NAME - The deployment name of the computer-use-preview model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -45,7 +45,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py index 65b027d6e281..c2c85a6c9906 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py @@ -23,7 +23,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) (Optional) COMPUTER_USE_MODEL_DEPLOYMENT_NAME - The deployment name of the computer-use-preview model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py index c780ff71b510..20bcd70f597d 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) FABRIC_PROJECT_CONNECTION_ID - The Fabric project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -59,7 +59,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py index c92e0cfc1e0f..9e14fdd34461 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search through product information.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py index c8ce27101fc2..4bac22ce828c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -63,7 +63,7 @@ agent = project_client.agents.create_version( agent_name="StreamingFileSearchAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search through product information and provide detailed responses. Use the file search tool to find relevant information before answering.", tools=[FileSearchTool(vector_store_ids=[vector_store.id])], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py index 1ca1c6ec2ec5..7f02ea13a7d6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main() -> None: @@ -63,7 +63,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="StreamingFileSearchAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search through product information and provide detailed responses. Use the file search tool to find relevant information before answering.", tools=[FileSearchTool(vector_store_ids=[vector_store.id])], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py index a8ee3ce16447..701201d55d99 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ def get_horoscope(sign: str) -> str: return f"{sign}: Next Tuesday you will befriend a baby otter." -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -69,7 +69,7 @@ def get_horoscope(sign: str) -> str: agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can use function tools.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py index 8f7a416abeb1..ccbc373e6585 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def get_horoscope(sign: str) -> str: @@ -70,7 +70,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can use function tools.", tools=[func_tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py index db372ec52647..5b9ea7011b3c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py @@ -28,9 +28,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) used by the Agent for understanding and responding to prompts. This is NOT the image generation model. 3) IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME - The deployment name of the image generation model (e.g. gpt-image-1) used by the ImageGenTool. @@ -53,7 +53,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -73,7 +73,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="Generate images based on user prompts", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py index 34010e739490..b7859d6483eb 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py @@ -28,9 +28,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) used by the Agent for understanding and responding to prompts. This is NOT the image generation model. 3) IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME - The deployment name of the image generation model (e.g. gpt-image-1) used by the ImageGenTool. @@ -53,7 +53,7 @@ from azure.ai.projects.models import PromptAgentDefinition, ImageGenTool load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main(): @@ -68,7 +68,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="Generate images based on user prompts", tools=[ImageGenTool(model=image_generation_model, quality="low", size="1024x1024")], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py index 95a4010ced59..c318f9004f1e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.", tools=[mcp_tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py index 0e980be777ad..cc12b02a8fdd 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main(): @@ -55,7 +55,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.", tools=tools, ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py index 27af3ffcd510..be1036a9fde8 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys with key equals to "Authorization" and value to be "Bearer ". @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -55,7 +55,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent7", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="Use MCP tools as needed", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py index 7d25ec8836ae..71ec422987a4 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys with key equals to "Authorization" and value to be "Bearer ". @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main(): @@ -58,7 +58,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="Use MCP tools as needed", tools=tools, ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py index 6888ad8263a5..26f9221631f1 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py @@ -23,9 +23,9 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -48,7 +48,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -96,7 +96,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py index dd3792a89249..6e959c0c3a88 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py @@ -23,9 +23,9 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -51,7 +51,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, @@ -90,7 +90,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", tools=[ MemorySearchPreviewTool( diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py index 33080b31243c..aba7820c69d6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv jsonref Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -38,7 +38,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -65,7 +65,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py index e4d56f1ba0f6..886288df69de 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv jsonref Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) OPENAPI_PROJECT_CONNECTION_ID - The OpenAPI project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -42,7 +42,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -75,7 +75,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py index 273b6c18a73e..6a108dfe83a9 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) SHAREPOINT_PROJECT_CONNECTION_ID - The SharePoint project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -59,7 +59,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="""You are a helpful agent that can use SharePoint tools to assist users. Use the available SharePoint tools to answer questions and perform tasks.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py index 2a0475de9bfa..133b99d589e0 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) A2A_PROJECT_CONNECTION_ID - The A2A project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -61,7 +61,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py index 6837cb0b13a2..01729d031854 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py @@ -26,9 +26,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -46,7 +46,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search the web", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py index d433fb8cf8e7..bf09f23786ff 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py @@ -26,9 +26,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,7 +42,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -56,7 +56,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent105", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search the web", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py index f086c37c2517..41d8d6f75aa2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py @@ -27,9 +27,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID - The Bing Custom Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -51,7 +51,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -70,7 +70,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that can search the web and bing", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py b/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py index f970221e5068..a867acc2106e 100644 --- a/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py +++ b/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - The name of a connection, as found in the "Connected resources" tab in the Management Center of your Microsoft Foundry project. @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] connection_name = os.environ["CONNECTION_NAME"] with ( diff --git a/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py b/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py index fce1328c7d36..e5814abf27fc 100644 --- a/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py +++ b/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - The name of a connection, as found in the "Connected resources" tab in the Management Center of your Microsoft Foundry project. @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] connection_name = os.environ["CONNECTION_NAME"] diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py index c0ef2fb68bf9..eb664060a12c 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -36,7 +36,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version_1 = os.environ.get("DATASET_VERSION_1", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py index ebf84028bbc8..7740507124d9 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -45,7 +45,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version_1 = os.environ.get("DATASET_VERSION_1", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py index 138431174b93..2a9aa4fc73a2 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py @@ -19,7 +19,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version = os.environ.get("DATASET_VERSION", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py index f6faeb5037bd..cee409590d7a 100644 --- a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py +++ b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. 3) MODEL_PUBLISHER - Optional. The publisher of the model to filter by. 4) MODEL_NAME - Optional. The name of the model to filter by. """ @@ -31,8 +31,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] model_publisher = os.environ.get("MODEL_PUBLISHER", "Microsoft") model_name = os.environ.get("MODEL_NAME", "Phi-4") diff --git a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py index 00c7d1a3e812..ae2a1151ba17 100644 --- a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py +++ b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. 3) MODEL_PUBLISHER - Optional. The publisher of the model to filter by. 4) MODEL_NAME - Optional. The name of the model to filter by. """ @@ -35,8 +35,8 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] - model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] model_publisher = os.environ.get("MODEL_PUBLISHER", "Microsoft") model_name = os.environ.get("MODEL_NAME", "Phi-4") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/README.md b/sdk/ai/azure-ai-projects/samples/evaluations/README.md index 078a9edc6cfc..628468aa20e4 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/README.md +++ b/sdk/ai/azure-ai-projects/samples/evaluations/README.md @@ -11,8 +11,8 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv ``` Set these environment variables: -- `PROJECT_ENDPOINT` - Your Azure AI Project endpoint (e.g., `https://.services.ai.azure.com/api/projects/`) -- `MODEL_DEPLOYMENT_NAME` - The model deployment name (e.g., `gpt-4o-mini`) +- `AZURE_AI_PROJECT_ENDPOINT` - Your Azure AI Project endpoint (e.g., `https://.services.ai.azure.com/api/projects/`) +- `AZURE_AI_MODEL_DEPLOYMENT_NAME` - The model deployment name (e.g., `gpt-4o-mini`) ## Sample Index @@ -94,8 +94,8 @@ Located in the [agentic_evaluators](https://github.com/Azure/azure-sdk-for-pytho ```bash # Set environment variables -export PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" -export MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Replace with your model +export AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Replace with your model # Run a sample python sample_evaluations_builtin_with_inline_data.py diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py index 19ebfbc845fa..13bbaf3726bc 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py index 0e747921b2dd..dc0a7199df78 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py index 49f206917552..1650b0d1cc5d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py @@ -28,7 +28,7 @@ def run_evaluator( data_mapping: dict[str, str], ) -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py index d54b7e664ed0..df15b21b6699 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -36,7 +36,7 @@ def _get_evaluator_initialization_parameters(evaluator_name: str) -> dict[str, s if evaluator_name == "task_navigation_efficiency": return {"matching_mode": "exact_match"} # Can be "exact_match", "in_order_match", or "any_order_match" else: - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini return {"deployment_name": model_deployment_name} diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py index 4832d3f1d926..654adfb1faec 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py index 06861dcb05ea..e434a78db732 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py index 0ca65a06c49c..1d3cc28dc6cb 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py index a73734676532..cd6ce53055ea 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py index c884df5ecf08..b2f5381c54e4 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py index b5439678f8eb..92d39f2ddddc 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py index 9383602a6194..6c9bc015d529 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ @@ -42,7 +42,7 @@ def main() -> None: endpoint = os.environ.get( - "PROJECT_ENDPOINT", "" + "AZURE_AI_PROJECT_ENDPOINT", "" ) # Sample : https://.services.ai.azure.com/api/projects/ with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py index c5384ce3c16c..c2f8980e503e 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py index ef82d24014cd..5404c8bb1183 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -44,9 +44,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py index 22dcc2c0f242..5809882fb1ee 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py index 287a36a417f5..3437f8fd674f 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py index eb21c7c1042e..e7646aeaabe8 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py index b3c6d2a45a27..7cafd28186f2 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py @@ -19,10 +19,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,8 +39,8 @@ from openai.types.evals.run_retrieve_response import RunRetrieveResponse load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini # [START agent_evaluation_basic] with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py index dae8f5f97596..9c81014daa52 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py @@ -19,10 +19,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ agent = project_client.agents.create_version( agent_name=os.environ["AZURE_AI_AGENT_NAME"], definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py index b69f22681c64..37d7244c3808 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -40,8 +40,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Define a function tool for the model to use func_tool = FunctionTool( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py index 7a37829a1110..7c38cb9a2d5d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py @@ -28,10 +28,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -50,7 +50,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -63,7 +63,7 @@ agent = project_client.agents.create_version( agent_name=os.environ["AZURE_AI_AGENT_NAME"], definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py index ff0d08897d8d..255ea0c2e660 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py index abd5526eb45b..c3227497204d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. """ @@ -42,8 +42,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py index fc00c7831b57..b3dfb581b3ce 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. For Custom Prompt Based Evaluators: @@ -75,8 +75,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py index d7a7886e5900..77093c5d704b 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -51,11 +51,11 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") if not model_deployment_name: - raise ValueError("MODEL_DEPLOYMENT_NAME environment variable is not set") + raise ValueError("AZURE_AI_MODEL_DEPLOYMENT_NAME environment variable is not set") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py index b830d1111548..c0fe4424bc85 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -47,7 +47,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -64,7 +64,7 @@ TestingCriterionLabelModel( type="label_model", name="sentiment_analysis", - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input=[ { "role": "developer", diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py index 4d1f0b66289c..8ad0899d2a01 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -39,8 +39,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py index 0aebda63159e..c113c8b67e06 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. 4) DATASET_VERSION - Optional. The version of the Dataset to create and use in this sample. 5) DATA_FOLDER - Optional. The folder path where the data files for upload are located. @@ -44,8 +44,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") dataset_name = os.environ.get("DATASET_NAME", "") dataset_version = os.environ.get("DATASET_VERSION", "1") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py index 1cf2e9328207..fa4ec52105a3 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -41,9 +41,9 @@ endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini # Construct the paths to the data folder and data file used in this sample script_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py index b2d9b79453b6..9d968bbbf296 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py @@ -18,9 +18,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -42,10 +42,10 @@ client = OpenAI( api_key=get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default"), - base_url=os.environ["PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini data_source_config = DataSourceConfigCustom( { diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py index e24649f8fc78..33aff799eb4f 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py @@ -19,12 +19,12 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-monitor-query Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) APPINSIGHTS_RESOURCE_ID - Required. The Azure Application Insights resource ID that stores agent traces. It has the form: /subscriptions//resourceGroups//providers/Microsoft.Insights/components/. 3) AGENT_ID - Required. The agent identifier emitted by the Azure tracing integration, used to filter traces. - 4) MODEL_DEPLOYMENT_NAME - Required. The Azure OpenAI deployment name to use with the built-in evaluators. + 4) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The Azure OpenAI deployment name to use with the built-in evaluators. 5) TRACE_LOOKBACK_HOURS - Optional. Number of hours to look back when querying traces and in the evaluation run. Defaults to 1. """ @@ -44,12 +44,12 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] appinsights_resource_id = os.environ[ "APPINSIGHTS_RESOURCE_ID" ] # Sample : /subscriptions//resourceGroups//providers/Microsoft.Insights/components/ agent_id = os.environ["AGENT_ID"] -model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] +model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] trace_query_hours = int(os.environ.get("TRACE_LOOKBACK_HOURS", "1")) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py index e382f1ce7c3d..ecb0f7fc86c7 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -39,8 +39,8 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py index 7f14453d4e26..0f9377ef9fbf 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv pillow Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -47,8 +47,8 @@ file_path = os.path.abspath(__file__) folder_path = os.path.dirname(file_path) -endpoint = os.environ["PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") def image_to_data_uri(image_path: str) -> str: diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py index c66866f7ed27..8b0170031dd3 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -68,7 +68,7 @@ ) print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") - model = os.environ["MODEL_DEPLOYMENT_NAME"] + model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] data_source = { "type": "azure_ai_target_completions", "source": { diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py index 6f75a6bad70f..6516c637796e 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) AZURE_AI_AGENT_NAME - Required. The name of the Agent to perform red teaming evaluation on. """ @@ -45,7 +45,7 @@ def main() -> None: load_dotenv() # - endpoint = os.environ.get("PROJECT_ENDPOINT", "") + endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") agent_name = os.environ.get("AZURE_AI_AGENT_NAME", "") with ( @@ -56,7 +56,7 @@ def main() -> None: agent_version = project_client.agents.create_version( agent_name=agent_name, definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py index fcde3de826ca..29397e006414 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-mgmt-authorization azure-mgmt-resource Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) AZURE_SUBSCRIPTION_ID - Required for RBAC assignment. The Azure subscription ID where the project is located. 3) AZURE_RESOURCE_GROUP_NAME - Required for RBAC assignment. The resource group name where the project is located. @@ -75,13 +75,13 @@ def assign_rbac(): """ load_dotenv() - endpoint = os.environ.get("PROJECT_ENDPOINT", "") + endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID", "") resource_group_name = os.environ.get("AZURE_RESOURCE_GROUP_NAME", "") if not endpoint or not subscription_id or not resource_group_name: print( - "Error: PROJECT_ENDPOINT, AZURE_SUBSCRIPTION_ID, and AZURE_RESOURCE_GROUP_NAME environment variables are required" + "Error: AZURE_AI_PROJECT_ENDPOINT, AZURE_SUBSCRIPTION_ID, and AZURE_RESOURCE_GROUP_NAME environment variables are required" ) return @@ -214,7 +214,7 @@ def assign_rbac(): def schedule_dataset_evaluation() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] dataset_name = os.environ.get("DATASET_NAME", "") dataset_version = os.environ.get("DATASET_VERSION", "1") # Construct the paths to the data folder and data file used in this sample @@ -327,7 +327,7 @@ def schedule_dataset_evaluation() -> None: def schedule_redteam_evaluation() -> None: load_dotenv() # - endpoint = os.environ.get("PROJECT_ENDPOINT", "") + endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") agent_name = os.environ.get("AZURE_AI_AGENT_NAME", "") # Construct the paths to the data folder and data file used in this sample @@ -343,7 +343,7 @@ def schedule_redteam_evaluation() -> None: agent_version = project_client.agents.create_version( agent_name=agent_name, definition=PromptAgentDefinition( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files.py b/sdk/ai/azure-ai-projects/samples/files/sample_files.py index ba156aff897e..57f8e3fb3187 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files.py @@ -16,7 +16,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) FILE_PATH - Optional. Path to the file to upload. Defaults to the `data` folder. """ @@ -29,7 +29,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] script_dir = Path(__file__).parent file_path = os.environ.get("FILE_PATH", os.path.join(script_dir, "data", "test_file.jsonl")) diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py index 0fbdcb80223c..d6bb1491a678 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py @@ -16,7 +16,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) FILE_PATH - Optional. Path to the file to upload. Defaults to the `data` folder. """ @@ -30,7 +30,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] script_dir = Path(__file__).parent file_path = os.environ.get("FILE_PATH", os.path.join(script_dir, "data", "test_file.jsonl")) diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py index 2e66c9edfb27..b8fe46419490 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4o` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "dpo_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "dpo_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py index 340159571043..4e7b5dc91ec0 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4o` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "dpo_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "dpo_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py index 5918ff361cdf..c4a44e2d727f 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "Ministral-3B") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py index 01267d2d3ecf..2fcd5ee8e93c 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `Ministral-3B` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "Ministral-3B") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py index 576587bfb4cc..6b1bed171863 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `o4-mini` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "o4-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "rft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "rft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py index 0034551f6005..701bf384731f 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `o4-mini` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "o4-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "rft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "rft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py index 4edd2d93f1ee..626af3c4bedd 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py @@ -21,7 +21,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-mgmt-cognitiveservices Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -43,7 +43,7 @@ load_dotenv() # For fine-tuning -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4.1") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py index 47d3136f2ced..cd967034fd92 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py @@ -21,7 +21,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp azure-mgmt-cognitiveservices Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -43,7 +43,7 @@ load_dotenv() # For fine-tuning -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4.1") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py index 594e651a917d..fd865b8d8ca1 100644 --- a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py +++ b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) INDEX_NAME - Optional. The name of the Index to create and use in this sample. 3) INDEX_VERSION - Optional. The version of the Index to create and use in this sample. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] index_name = os.environ.get("INDEX_NAME", "index-test") index_version = os.environ.get("INDEX_VERSION", "1.0") ai_search_connection_name = os.environ.get("AI_SEARCH_CONNECTION_NAME", "my-ai-search-connection-name") diff --git a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py index 1cd9c00f1328..9ace80a10df8 100644 --- a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py +++ b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) INDEX_NAME - Optional. The name of the Index to create and use in this sample. 3) INDEX_VERSION - Optional. The version of the Index to create and use in this sample. @@ -37,7 +37,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] index_name = os.environ.get("INDEX_NAME", "index-test") index_version = os.environ.get("INDEX_VERSION", "1.0") ai_search_connection_name = os.environ.get("AI_SEARCH_CONNECTION_NAME", "my-ai-search-connection-name") diff --git a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py index 4623807d024d..d09bb48a72ad 100644 --- a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py @@ -8,7 +8,7 @@ DESCRIPTION: This sample demonstrates how to directly interact with MCP (Model Context Protocol) tools using the low-level MCP client library to connect to the Foundry Project's MCP tools API: - {PROJECT_ENDPOINT}/mcp_tools?api-version=2025-05-15-preview + {AZURE_AI_PROJECT_ENDPOINT}/mcp_tools?api-version=2025-05-15-preview For agent-based MCP tool usage, see samples in samples/agents/tools/sample_agent_mcp.py and related files in that directory. @@ -29,7 +29,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv mcp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) IMAGE_GEN_DEPLOYMENT_NAME - The deployment name of the image generation model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -64,7 +64,7 @@ # Enable httpx logging to see HTTP requests at the same level logging.getLogger("httpx").setLevel(getattr(logging, log_level, logging.CRITICAL)) -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py index 85f8e3e5aec9..6af7c9c90f3c 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py @@ -24,7 +24,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -46,7 +46,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py index bde8a22a1c3e..f8b9643f7547 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py @@ -24,7 +24,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -50,7 +50,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py index ff307a66cbb9..0d3cf82f4fbe 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py @@ -22,7 +22,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -43,7 +43,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py index fe12d1db07ee..6999a6c154ca 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py @@ -23,7 +23,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -48,7 +48,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py index cf116e910d08..94d1abdab835 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py @@ -20,7 +20,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -37,7 +37,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py index 23fc4782e98e..730dd3a53534 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py @@ -20,7 +20,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py index bdf4936f8f6c..918ae00569e5 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. 3) MODEL_ENDPOINT - Required. The Azure AI Model endpoint, as found in the overview page of your Microsoft Foundry project. Example: https://.services.ai.azure.com 4) MODEL_API_KEY - Required. The API key for your Azure AI Model. @@ -38,11 +38,11 @@ load_dotenv() endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ model_endpoint = os.environ["MODEL_ENDPOINT"] # Sample : https://.services.ai.azure.com model_api_key = os.environ["MODEL_API_KEY"] -model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini +model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py index 54170b0d3492..261b0f9aaedc 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. 3) MODEL_ENDPOINT - Required. The Azure AI Model endpoint, as found in the overview page of your Microsoft Foundry project. Example: https://.services.ai.azure.com 4) MODEL_API_KEY - Required. The API key for your Azure AI Model. @@ -43,11 +43,11 @@ async def sample_red_team_async() -> None: """Demonstrates how to perform Red Team operations using the AIProjectClient.""" endpoint = os.environ[ - "PROJECT_ENDPOINT" + "AZURE_AI_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ model_endpoint = os.environ["MODEL_ENDPOINT"] # Sample : https://.services.ai.azure.com model_api_key = os.environ["MODEL_API_KEY"] - model_deployment_name = os.environ["MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini + model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py index 062aef48577e..436237b03c46 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -40,13 +40,13 @@ # [START responses] with project_client.get_openai_client() as openai_client: response = openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py index 0501cc142cbe..b68ab13eddd7 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async def main() -> None: @@ -43,13 +43,13 @@ async def main() -> None: project_client.get_openai_client() as openai_client, ): response = await openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = await openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py index 39375d948e2d..9c5d8b656bd7 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py @@ -19,9 +19,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,11 +34,11 @@ openai = OpenAI( api_key=get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default"), - base_url=os.environ["PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) response = openai.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="How many feet are in a mile?", ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py index 47b46923f9b3..8a2934ff7418 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py @@ -19,9 +19,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,13 +42,13 @@ async def main() -> None: openai = AsyncOpenAI( api_key=get_bearer_token_provider(credential, "https://ai.azure.com/.default"), - base_url=os.environ["PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) async with openai: response = await openai.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input="How many feet are in a mile?", ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py index 1299f15d638c..542d956b1cf0 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] def image_to_base64(image_path: str) -> str: @@ -71,6 +71,6 @@ def image_to_base64(image_path: str) -> str: ], } ], - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], ) print(f"Response output: {response.output_text}") diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py index 8d7f60673c2b..86f3b2d1fa47 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -43,7 +43,7 @@ ): with openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input=[ {"role": "user", "content": "Tell me about the capital city of France"}, ], diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py index 6508bd40deb4..110bb0b1c4be 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -42,7 +42,7 @@ ): with openai_client.responses.stream( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], input=[ {"role": "user", "content": "Tell me about the capital city of France"}, ], diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py index a781b8152edc..d1c75654083a 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,7 +42,7 @@ class CalendarEvent(BaseModel): participants: list[str] -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ class CalendarEvent(BaseModel): project_client.get_openai_client() as openai_client, ): response = openai_client.responses.create( - model=os.environ["MODEL_DEPLOYMENT_NAME"], + model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], instructions=""" Extracts calendar event information from the input messages, and return it in the desired structured output format. diff --git a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py index b5ef4f48234a..61f11436620b 100644 --- a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py +++ b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. """ @@ -28,7 +28,7 @@ load_dotenv() -endpoint = os.environ["PROJECT_ENDPOINT"] +endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py index 4e98aff356bd..7554370401f4 100644 --- a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py +++ b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your + 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ async def main() -> None: - endpoint = os.environ["PROJECT_ENDPOINT"] + endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py index c8a6ebc74921..d7aede04fb65 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py @@ -273,7 +273,7 @@ def _test_agent_creation_with_tracing_content_recording_enabled_impl(self, use_e with self.create_client(operation_group="tracing", **kwargs) as project_client: - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") print(f"Using model deployment: {model}") agent_definition = PromptAgentDefinition( @@ -387,7 +387,7 @@ def _test_agent_creation_with_tracing_content_recording_disabled_impl(self, use_ with self.create_client(operation_group="agents", **kwargs) as project_client: - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") agent_definition = PromptAgentDefinition( model=model, instructions="You are a helpful AI assistant. Always be polite and provide accurate information.", @@ -609,7 +609,7 @@ def _test_agent_with_structured_output_with_instructions_impl( operation_group = "tracing" if content_recording_enabled else "agents" with self.create_client(operation_group=operation_group, **kwargs) as project_client: - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") test_schema = { "type": "object", @@ -797,7 +797,7 @@ def _test_agent_with_structured_output_without_instructions_impl( operation_group = "tracing" if content_recording_enabled else "agents" with self.create_client(operation_group=operation_group, **kwargs) as project_client: - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") test_schema = { "type": "object", diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py index 80f20632fd98..96184005a350 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py @@ -75,7 +75,7 @@ async def _test_create_agent_with_tracing_content_recording_enabled_impl(self, u assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with project_client: agent_definition = PromptAgentDefinition( @@ -186,7 +186,7 @@ async def _test_agent_creation_with_tracing_content_recording_disabled_impl(self assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="agents", **kwargs) - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with project_client: agent_definition = PromptAgentDefinition( @@ -406,7 +406,7 @@ async def _test_agent_with_structured_output_with_instructions_impl( project_client = self.create_async_client(operation_group=operation_group, **kwargs) async with project_client: - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") test_schema = { "type": "object", @@ -591,7 +591,7 @@ async def _test_agent_with_structured_output_without_instructions_impl( project_client = self.create_async_client(operation_group=operation_group, **kwargs) async with project_client: - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") test_schema = { "type": "object", diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py index 23a9116edd60..7b8471717f2b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py @@ -78,7 +78,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: openai_client = project_client.get_openai_client() # Get the model deployment name from test parameters - model_deployment_name = kwargs.get("model_deployment_name") + model_deployment_name = kwargs.get("azure_ai_model_deployment_name") return openai_client, model_deployment_name @@ -234,7 +234,7 @@ def _test_sync_non_streaming_with_content_recording_impl(self, use_events, **kwa with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -356,7 +356,7 @@ def _test_sync_non_streaming_without_content_recording_impl(self, use_events, ** with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -479,7 +479,7 @@ def _test_sync_streaming_with_content_recording_impl(self, use_events, **kwargs) with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -656,7 +656,7 @@ def test_sync_conversations_create(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -701,7 +701,7 @@ def test_sync_list_conversation_items_with_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -778,7 +778,7 @@ def test_sync_list_conversation_items_without_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -887,7 +887,7 @@ def _test_sync_non_streaming_without_conversation_impl(self, use_events, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") with project_client: # Get the OpenAI client from the project client @@ -994,7 +994,7 @@ def _test_sync_function_tool_with_content_recording_non_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -1253,7 +1253,7 @@ def _test_sync_function_tool_with_content_recording_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -1577,7 +1577,7 @@ def _test_sync_function_tool_without_content_recording_non_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -1813,7 +1813,7 @@ def _test_sync_function_tool_without_content_recording_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -2131,7 +2131,7 @@ def test_sync_function_tool_list_conversation_items_with_content_recording(self, with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -2281,7 +2281,7 @@ def test_sync_function_tool_list_conversation_items_without_content_recording(se with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -2429,7 +2429,7 @@ def test_sync_multiple_text_inputs_with_content_recording_non_streaming(self, ** with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2526,7 +2526,7 @@ def test_sync_multiple_text_inputs_with_content_recording_streaming(self, **kwar with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2631,7 +2631,7 @@ def test_sync_multiple_text_inputs_without_content_recording_non_streaming(self, with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2723,7 +2723,7 @@ def test_sync_multiple_text_inputs_without_content_recording_streaming(self, **k with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = client.conversations.create() @@ -2824,7 +2824,7 @@ def _test_image_only_content_off_binary_off_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -2929,7 +2929,7 @@ def _test_image_only_content_off_binary_on_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3033,7 +3033,7 @@ def _test_image_only_content_on_binary_off_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3137,7 +3137,7 @@ def _test_image_only_content_on_binary_on_non_streaming_impl(self, use_events, * with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3245,7 +3245,7 @@ def _test_text_and_image_content_off_binary_off_non_streaming_impl(self, use_eve with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3354,7 +3354,7 @@ def _test_text_and_image_content_off_binary_on_non_streaming_impl(self, use_even with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3463,7 +3463,7 @@ def _test_text_and_image_content_on_binary_off_non_streaming_impl(self, use_even with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3571,7 +3571,7 @@ def _test_text_and_image_content_on_binary_on_non_streaming_impl(self, use_event with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3683,7 +3683,7 @@ def _test_image_only_content_off_binary_off_streaming_impl(self, use_events, **k with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3796,7 +3796,7 @@ def _test_image_only_content_off_binary_on_streaming_impl(self, use_events, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -3908,7 +3908,7 @@ def _test_image_only_content_on_binary_off_streaming_impl(self, use_events, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4020,7 +4020,7 @@ def _test_image_only_content_on_binary_on_streaming_impl(self, use_events, **kwa with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4136,7 +4136,7 @@ def _test_text_and_image_content_off_binary_off_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4253,7 +4253,7 @@ def _test_text_and_image_content_off_binary_on_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4370,7 +4370,7 @@ def _test_text_and_image_content_on_binary_off_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4486,7 +4486,7 @@ def _test_text_and_image_content_on_binary_on_streaming_impl(self, use_events, * with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4601,7 +4601,7 @@ def test_responses_stream_method_with_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4654,7 +4654,7 @@ def test_responses_stream_method_without_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = client.conversations.create() @@ -4709,7 +4709,7 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -4823,7 +4823,7 @@ def test_responses_stream_method_with_tools_without_content_recording(self, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -4960,7 +4960,7 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): assert True == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") openai_client = project_client.get_openai_client() # Create Teacher Agent @@ -5159,7 +5159,7 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): assert False == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") openai_client = project_client.get_openai_client() workflow_yaml = """ @@ -5276,7 +5276,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): assert True == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") openai_client = project_client.get_openai_client() # Create Teacher Agent @@ -5478,7 +5478,7 @@ def test_workflow_agent_streaming_without_content_recording(self, **kwargs): assert False == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") openai_client = project_client.get_openai_client() workflow_yaml = """ diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py index 490715fa0333..8f2b18126533 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py @@ -59,7 +59,7 @@ async def _test_async_non_streaming_with_content_recording_impl(self, use_events assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -165,7 +165,7 @@ async def _test_async_streaming_with_content_recording_impl(self, use_events, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -277,7 +277,7 @@ async def test_async_conversations_create(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -325,7 +325,7 @@ async def test_async_list_conversation_items_with_content_recording(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: # Get the OpenAI client from the project client @@ -417,7 +417,7 @@ async def _test_async_function_tool_with_content_recording_streaming_impl( async with project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -678,7 +678,7 @@ async def _test_async_function_tool_without_content_recording_streaming_impl( async with project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool func_tool = FunctionTool( @@ -924,7 +924,7 @@ async def test_async_multiple_text_inputs_with_content_recording_non_streaming(s async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -1021,7 +1021,7 @@ async def test_async_multiple_text_inputs_with_content_recording_streaming(self, async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -1126,7 +1126,7 @@ async def test_async_multiple_text_inputs_without_content_recording_non_streamin async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -1225,7 +1225,7 @@ async def test_async_image_only_content_off_binary_off_non_streaming(self, **kwa assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1297,7 +1297,7 @@ async def test_async_image_only_content_off_binary_on_non_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1368,7 +1368,7 @@ async def test_async_image_only_content_on_binary_off_non_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1439,7 +1439,7 @@ async def test_async_image_only_content_on_binary_on_non_streaming(self, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1514,7 +1514,7 @@ async def test_async_text_and_image_content_off_binary_off_non_streaming(self, * assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1590,7 +1590,7 @@ async def test_async_text_and_image_content_off_binary_on_non_streaming(self, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1665,7 +1665,7 @@ async def test_async_text_and_image_content_on_binary_off_non_streaming(self, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1740,7 +1740,7 @@ async def test_async_text_and_image_content_on_binary_on_non_streaming(self, **k assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1819,7 +1819,7 @@ async def test_async_image_only_content_off_binary_off_streaming(self, **kwargs) assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1899,7 +1899,7 @@ async def test_async_image_only_content_off_binary_on_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -1978,7 +1978,7 @@ async def test_async_image_only_content_on_binary_off_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2057,7 +2057,7 @@ async def test_async_image_only_content_on_binary_on_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2140,7 +2140,7 @@ async def test_async_text_and_image_content_off_binary_off_streaming(self, **kwa assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2223,7 +2223,7 @@ async def test_async_text_and_image_content_off_binary_on_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2306,7 +2306,7 @@ async def test_async_text_and_image_content_on_binary_off_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2389,7 +2389,7 @@ async def test_async_text_and_image_content_on_binary_on_streaming(self, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -2474,7 +2474,7 @@ async def test_async_multiple_text_inputs_without_content_recording_streaming(se async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Create a conversation conversation = await client.conversations.create() @@ -2582,7 +2582,7 @@ async def test_async_responses_stream_method_with_content_recording(self, **kwar async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = await client.conversations.create() @@ -2662,7 +2662,7 @@ async def test_async_responses_stream_method_without_content_recording(self, **k async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") conversation = await client.conversations.create() @@ -2750,7 +2750,7 @@ async def _test_async_responses_stream_method_with_tools_with_content_recording_ async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -2983,7 +2983,7 @@ async def _test_async_responses_stream_method_with_tools_without_content_recordi async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") # Define a function tool function_tool = FunctionTool( @@ -3214,7 +3214,7 @@ async def test_async_workflow_agent_non_streaming_with_content_recording(self, * assert True == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: # Create a simple workflow agent @@ -3331,7 +3331,7 @@ async def test_async_workflow_agent_non_streaming_without_content_recording(self assert False == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: workflow_yaml = """ @@ -3454,7 +3454,7 @@ async def test_async_workflow_agent_streaming_with_content_recording(self, **kwa assert True == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: workflow_yaml = """ @@ -3575,7 +3575,7 @@ async def test_async_workflow_agent_streaming_without_content_recording(self, ** assert False == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: workflow_yaml = """ @@ -3708,7 +3708,7 @@ async def _test_async_prompt_agent_with_responses_non_streaming_impl( assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() @@ -3846,7 +3846,7 @@ async def _test_async_prompt_agent_with_responses_streaming_impl( assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") async with project_client: client = project_client.get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py index 7cb41a48cd3f..f68d4fdae952 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py @@ -59,7 +59,7 @@ def test_sync_browser_automation_non_streaming_with_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -194,7 +194,7 @@ def test_sync_browser_automation_non_streaming_without_content_recording(self, * assert not AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -320,7 +320,7 @@ def test_sync_browser_automation_streaming_with_content_recording(self, **kwargs self.setup_telemetry() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -448,7 +448,7 @@ def test_sync_browser_automation_streaming_without_content_recording(self, **kwa self.setup_telemetry() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py index 4b2454c510cb..46918894c2fb 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py @@ -62,7 +62,7 @@ async def test_async_browser_automation_non_streaming_with_content_recording(sel assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -193,7 +193,7 @@ async def test_async_browser_automation_non_streaming_without_content_recording( assert not AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -315,7 +315,7 @@ async def test_async_browser_automation_streaming_with_content_recording(self, * self.setup_telemetry() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -440,7 +440,7 @@ async def test_async_browser_automation_streaming_without_content_recording(self self.setup_telemetry() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py index b23ea2b0e5e6..331d64b9aaa8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py @@ -66,7 +66,7 @@ def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwar assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -254,7 +254,7 @@ def test_sync_code_interpreter_non_streaming_without_content_recording(self, **k assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -445,7 +445,7 @@ def test_sync_code_interpreter_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -636,7 +636,7 @@ def test_sync_code_interpreter_streaming_without_content_recording(self, **kwarg assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py index 007b78ce5bd1..7e5512b6fbbe 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py @@ -67,7 +67,7 @@ async def test_async_code_interpreter_non_streaming_with_content_recording(self, assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -254,7 +254,7 @@ async def test_async_code_interpreter_non_streaming_without_content_recording(se assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -445,7 +445,7 @@ async def test_async_code_interpreter_streaming_with_content_recording(self, **k assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -636,7 +636,7 @@ async def test_async_code_interpreter_streaming_without_content_recording(self, assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py index 76faa8c49ff4..ca4f301212f3 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py @@ -53,7 +53,7 @@ def test_sync_file_search_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -262,7 +262,7 @@ def test_sync_file_search_non_streaming_without_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -469,7 +469,7 @@ def test_sync_file_search_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -674,7 +674,7 @@ def test_sync_file_search_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py index 647d24530f54..fd6c36261449 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py @@ -54,7 +54,7 @@ async def test_async_file_search_non_streaming_with_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -263,7 +263,7 @@ async def test_async_file_search_non_streaming_without_content_recording(self, * assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -470,7 +470,7 @@ async def test_async_file_search_streaming_with_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -675,7 +675,7 @@ async def test_async_file_search_streaming_without_content_recording(self, **kwa assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py index 3ef49deac892..4c5c453a3e23 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py @@ -59,7 +59,7 @@ def _test_sync_mcp_non_streaming_with_content_recording_impl(self, use_events, * assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -389,7 +389,7 @@ def _test_sync_mcp_non_streaming_without_content_recording_impl(self, use_events assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -706,7 +706,7 @@ def _test_sync_mcp_streaming_with_content_recording_impl(self, use_events, **kwa assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -982,7 +982,7 @@ def _test_sync_mcp_streaming_without_content_recording_impl(self, use_events, ** assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py index c218a653295c..d9e82e2951e8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py @@ -60,7 +60,7 @@ async def _test_async_mcp_non_streaming_with_content_recording_impl(self, use_ev assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -389,7 +389,7 @@ async def _test_async_mcp_non_streaming_without_content_recording_impl(self, use assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -708,7 +708,7 @@ async def _test_async_mcp_streaming_with_content_recording_impl(self, use_events assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -987,7 +987,7 @@ async def _test_async_mcp_streaming_without_content_recording_impl(self, use_eve assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py index f75e8d9694e7..a198327679c3 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py @@ -41,7 +41,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: openai_client = project_client.get_openai_client() # Get the model deployment name from test parameters - model_deployment_name = kwargs.get("model_deployment_name") + model_deployment_name = kwargs.get("azure_ai_model_deployment_name") return openai_client, model_deployment_name diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py index 492c49c9d7af..bec6cfa9f2be 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py @@ -206,7 +206,7 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -371,7 +371,7 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -538,7 +538,7 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: @@ -706,7 +706,7 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py index 1ce2aa436e88..e366e1ec3ef5 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py @@ -205,7 +205,7 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -366,7 +366,7 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -531,7 +531,7 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: @@ -697,7 +697,7 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("model_deployment_name") + deployment_name = kwargs.get("azure_ai_model_deployment_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py index 92fca677d439..1cc36a6b0455 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py @@ -48,7 +48,7 @@ def test_agent_responses_crud(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -158,7 +158,7 @@ def test_agent_responses_crud(self, **kwargs): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_agent_responses_with_structured_output(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py index 449dead94abc..b710851c366f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py @@ -23,7 +23,7 @@ class TestAgentResponsesCrudAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_responses_crud_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) @@ -129,7 +129,7 @@ async def test_agent_responses_crud_async(self, **kwargs): @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_responses_with_structured_output_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py index 724baf8c022b..10414b7a59d1 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py @@ -39,7 +39,7 @@ def test_agents_crud(self, **kwargs): GET /agents/{agent_name}/versions/{agent_version} project_client.agents.get_version() """ print("\n") - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") project_client = self.create_client(operation_group="agents", **kwargs) first_agent_name = "MyAgent1" second_agent_name = "MyAgent2" diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py index 9853f973e098..e9776b7e6257 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py @@ -24,7 +24,7 @@ async def test_agents_crud_async(self, **kwargs): It then gets, lists, and deletes them, validating at each step. It uses different ways of creating agents: strongly typed, dictionary, and IO[bytes]. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") project_client = self.create_async_client(operation_group="agents", **kwargs) first_agent_name = "MyAgent1" second_agent_name = "MyAgent2" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py index e835e18936b3..3953bf1c76d2 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py @@ -40,7 +40,7 @@ def test_calculate_and_save(self, **kwargs): 2. Function Tool: Saves the computed result """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -100,7 +100,7 @@ def test_generate_data_and_report(self, **kwargs): 2. Function Tool: Creates a report with the computed statistics """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py index a6da388ec0b9..16f2c2c1ba41 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py @@ -39,7 +39,7 @@ def test_find_and_analyze_data(self, **kwargs): 2. Code Interpreter: Agent calculates the average of those numbers """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -121,7 +121,7 @@ def test_analyze_code_file(self, **kwargs): 2. Code Interpreter: Agent executes the code and returns the computed result """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py index b127a14bc89d..f67e95c020a8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py @@ -32,7 +32,7 @@ def test_data_analysis_workflow(self, **kwargs): Test data analysis workflow: upload data, search, save results. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -163,7 +163,7 @@ def test_empty_vector_store_handling(self, **kwargs): Test how agent handles empty vector store (no files uploaded). """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -242,7 +242,7 @@ def test_python_code_file_search(self, **kwargs): 2. Function Tool: Agent saves the code review findings """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -372,7 +372,7 @@ def test_multi_turn_search_and_save_workflow(self, **kwargs): - Context retention across searches and function calls """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py index 3d2d12353dfb..61d572fa0a37 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py @@ -43,7 +43,7 @@ def test_complete_analysis_workflow(self, **kwargs): 3. Function Tool: Agent saves the computed results """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py index 1bc2d553caa2..1ae26a32a1a4 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py @@ -39,7 +39,7 @@ def test_file_search_and_function_with_conversation(self, **kwargs): - Verifying conversation state preserves all tool interactions """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py index d2a9add36b0a..02d341051ec8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py @@ -81,7 +81,7 @@ def test_agent_ai_search_question_answering(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Get AI Search connection and index from environment ai_search_connection_id = kwargs.get("ai_search_project_connection_id") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py index f7b55542a649..5bc67d9a2833 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py @@ -148,7 +148,7 @@ async def test_agent_ai_search_question_answering_async_parallel(self, **kwargs) DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py index 300914195e05..78ec18081aa0 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py @@ -45,7 +45,7 @@ def test_agent_bing_grounding(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Note: This test requires bing_project_connection_id environment variable # to be set with a valid Bing connection ID from the project @@ -145,7 +145,7 @@ def test_agent_bing_grounding_multiple_queries(self, **kwargs): Bing grounding and provide accurate responses with citations. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") bing_connection_id = kwargs.get("bing_project_connection_id") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py index f6531d9a1c6b..7b5d1ea27680 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py @@ -41,7 +41,7 @@ def test_agent_code_interpreter_simple_math(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") agent_name = "code-interpreter-simple-agent" with ( @@ -125,7 +125,7 @@ def test_agent_code_interpreter_file_generation(self, **kwargs): DELETE /files/{file_id} openai_client.files.delete() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py index 84f722173fa7..d38d15b2bd0e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py @@ -28,7 +28,7 @@ async def test_agent_code_interpreter_simple_math_async(self, **kwargs): without any file uploads or downloads - just pure code execution. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") agent_name = "code-interpreter-simple-agent-async" async with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py index c398c3d6a1c4..e7408afe97fa 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py @@ -45,7 +45,7 @@ def test_agent_file_search(self, **kwargs): DELETE /vector_stores/{id} openai_client.vector_stores.delete() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -203,7 +203,7 @@ def test_agent_file_search_multi_turn_conversation(self, **kwargs): while using File Search to answer follow-up questions. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py index 420b43ffca2b..e3d96f5a4733 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py @@ -20,7 +20,7 @@ class TestAgentFileSearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_file_search_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, @@ -106,7 +106,7 @@ async def test_agent_file_search_multi_turn_conversation_async(self, **kwargs): while using File Search to answer follow-up questions. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py index 861f3602d450..e97814456771 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py @@ -42,7 +42,7 @@ def test_agent_file_search_stream(self, **kwargs): DELETE /vector_stores/{id} openai_client.vector_stores.delete() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py index 9bcad6d77fc2..fb4e627df2de 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py @@ -19,7 +19,7 @@ class TestAgentFileSearchStreamAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_file_search_stream_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py index 51216e5c6300..264bf97ebf73 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py @@ -41,7 +41,7 @@ def test_agent_function_tool(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") agent_name = "function-tool-agent" with ( @@ -172,7 +172,7 @@ def test_agent_function_tool_multi_turn_with_multiple_calls(self, **kwargs): - Ability to use previous function results in subsequent queries """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -381,7 +381,7 @@ def test_agent_function_tool_context_dependent_followup(self, **kwargs): remembering parameters from the first query. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py index 92b806f0935c..f4388b1ccfe9 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py @@ -28,7 +28,7 @@ async def test_agent_function_tool_async(self, **kwargs): 3. Receive function results and incorporate them into responses """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") agent_name = "function-tool-agent-async" # Setup @@ -160,7 +160,7 @@ async def test_agent_function_tool_multi_turn_with_multiple_calls_async(self, ** - Ability to use previous function results in subsequent queries """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) @@ -370,7 +370,7 @@ async def test_agent_function_tool_context_dependent_followup_async(self, **kwar remembering parameters from the first query. """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") # Setup async with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py index be27c5685b53..c0c515839aaf 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py @@ -41,7 +41,7 @@ def test_agent_image_generation(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") image_model = kwargs.get("image_generation_model_deployment_name") agent_name = "image-gen-agent" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py index 4450f75fd103..a4775afb16b9 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py @@ -21,7 +21,7 @@ class TestAgentImageGenerationAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_image_generation_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") image_model = kwargs.get("image_generation_model_deployment_name") agent_name = "image-gen-agent" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py index 243b8a62c71e..5723478f7569 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py @@ -48,7 +48,7 @@ def test_agent_mcp_basic(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -179,7 +179,7 @@ def test_agent_mcp_with_project_connection(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py index dfc0df69188b..36a951e79183 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py @@ -21,7 +21,7 @@ class TestAgentMCPAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_mcp_basic_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py index 2cc6b1023295..3a1bc4e44d0d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py @@ -54,7 +54,7 @@ def test_agent_memory_search(self, **kwargs): DELETE /memory_stores/{memory_store_name} project_client.beta.memory_stores.delete() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") embedding_model = kwargs.get("memory_store_embedding_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py index e1a888787053..dc6b69d22354 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py @@ -29,7 +29,7 @@ class TestAgentMemorySearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_memory_search_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") embedding_model = kwargs.get("memory_store_embedding_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py index 6905e4f27b6d..de8b85f19723 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py @@ -51,7 +51,7 @@ def test_agent_openapi(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py index a2da6989196e..1b3e87ef063a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py @@ -31,7 +31,7 @@ class TestAgentOpenApiAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_openapi_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py index 110241a76862..00fdf016e79d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py @@ -40,7 +40,7 @@ def test_function_tool_with_conversation(self, **kwargs): - Using conversation_id parameter """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -201,7 +201,7 @@ def test_file_search_with_conversation(self, **kwargs): - Conversation context retention """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -318,7 +318,7 @@ def test_code_interpreter_with_conversation(self, **kwargs): - Variables/state persistence across turns """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -403,7 +403,7 @@ def test_code_interpreter_with_file_in_conversation(self, **kwargs): - Server-side code execution with file access and chart generation """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") import os with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py index d242c7bb6c17..9a8f616e9d7f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py @@ -38,7 +38,7 @@ def test_agent_web_search(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py index 2a9bc17b5ee9..e11732ca4cac 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py @@ -18,7 +18,7 @@ class TestAgentWebSearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_web_search_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py index c29bbf42edcc..9e790b1f37c7 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py @@ -137,7 +137,7 @@ def test_datasets_upload_file(self, **kwargs): @recorded_by_proxy def test_datasets_upload_folder(self, **kwargs): - endpoint = kwargs.pop("project_endpoint") + endpoint = kwargs.pop("azure_ai_project_endpoint") print("\n=====> Endpoint:", endpoint) dataset_name = self.test_datasets_params["dataset_name_2"] diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py index 678bd1bedae2..724b6318b938 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py @@ -138,7 +138,7 @@ async def test_datasets_upload_file(self, **kwargs): @recorded_by_proxy_async async def test_datasets_upload_folder_async(self, **kwargs): - endpoint = kwargs.pop("project_endpoint") + endpoint = kwargs.pop("azure_ai_project_endpoint") print("\n=====> Endpoint:", endpoint) dataset_name = self.test_datasets_params["dataset_name_4"] diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py index a4dc057d9765..53132a89a396 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py @@ -18,8 +18,8 @@ class TestDeployments(TestBase): def test_deployments(self, **kwargs): model_publisher = "OpenAI" - model_name = kwargs.get("model_deployment_name") - model_deployment_name = kwargs.get("model_deployment_name") + model_name = kwargs.get("azure_ai_model_deployment_name") + model_deployment_name = kwargs.get("azure_ai_model_deployment_name") with self.create_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py index dba5495b0038..06f229c1e15b 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py @@ -18,8 +18,8 @@ class TestDeploymentsAsync(TestBase): async def test_deployments_async(self, **kwargs): model_publisher = "OpenAI" - model_name = kwargs.get("model_deployment_name") - model_deployment_name = kwargs.get("model_deployment_name") + model_name = kwargs.get("azure_ai_model_deployment_name") + model_deployment_name = kwargs.get("azure_ai_model_deployment_name") async with self.create_async_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index 51ed46e70849..d8e28452557f 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -334,11 +334,11 @@ def _test_deploy_and_infer_helper( subscription_id = kwargs.get("azure_subscription_id") resource_group = kwargs.get("azure_resource_group") - project_endpoint = kwargs.get("project_endpoint") + project_endpoint = kwargs.get("azure_ai_project_endpoint") if not all([subscription_id, resource_group, project_endpoint]): pytest.skip( - f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, project_endpoint) - skipping {test_prefix} deploy and infer test" + f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, azure_ai_project_endpoint) - skipping {test_prefix} deploy and infer test" ) account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index 117c06684914..be0cc2de95dc 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -345,11 +345,11 @@ async def _test_deploy_and_infer_helper_async( subscription_id = kwargs.get("azure_subscription_id") resource_group = kwargs.get("azure_resource_group") - project_endpoint = kwargs.get("project_endpoint") + project_endpoint = kwargs.get("azure_ai_project_endpoint") if not all([subscription_id, resource_group, project_endpoint]): pytest.skip( - f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, project_endpoint) - skipping {test_prefix} deploy and infer test" + f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, azure_ai_project_endpoint) - skipping {test_prefix} deploy and infer test" ) account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py index 77f354335d4a..5165f37ddb03 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py @@ -57,7 +57,7 @@ def test_responses(self, **kwargs): ------+---------------------------------------------+----------------------------------- POST /openai/responses client.responses.create() """ - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") client = self.create_client(operation_group="agents", **kwargs).get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py index 187bbde4a259..bf7252962dad 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py @@ -45,7 +45,7 @@ class TestResponsesAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.HTTPX) async def test_responses_async(self, **kwargs): - model = kwargs.get("model_deployment_name") + model = kwargs.get("azure_ai_model_deployment_name") client = self.create_async_client(operation_group="agents", **kwargs).get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/samples/README.md b/sdk/ai/azure-ai-projects/tests/samples/README.md index 4f2547678cbe..3296cb7c58ea 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/README.md +++ b/sdk/ai/azure-ai-projects/tests/samples/README.md @@ -67,7 +67,7 @@ class TestSamples(AzureRecordedTestCase): executor.execute() executor.validate_print_calls_by_llm( instructions=agent_tools_instructions, - project_endpoint=kwargs["project_endpoint"], + project_endpoint=kwargs["azure_ai_project_endpoint"], ) ``` @@ -106,7 +106,7 @@ class TestSamplesAsync(AzureRecordedTestCase): await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["project_endpoint"], + project_endpoint=kwargs["azure_ai_project_endpoint"], ) ``` @@ -122,8 +122,8 @@ from devtools_testutils import EnvironmentVariableLoader servicePreparer = functools.partial( EnvironmentVariableLoader, "", - project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - model_deployment_name="gpt-4o", + azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + azure_ai_model_deployment_name="gpt-4o", # add other sanitized vars here ) ``` @@ -154,8 +154,8 @@ If you need to remap the values provided by your fixtures to the environment-var ```python env_vars = { - "PROJECT_ENDPOINT": kwargs["TEST_AZURE_AI_PROJECT_ENDPOINT"], - "MODEL_DEPLOYMENT_NAME": kwargs["TEST_AZURE_AI_MODEL_DEPLOYMENT_NAME"], + "AZURE_AI_PROJECT_ENDPOINT": kwargs["TEST_AZURE_AI_PROJECT_ENDPOINT"], + "AZURE_AI_MODEL_DEPLOYMENT_NAME": kwargs["TEST_AZURE_AI_MODEL_DEPLOYMENT_NAME"], } executor = SyncSampleExecutor(self, sample_path, env_vars=env_vars, **kwargs) ``` diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 0dac34211592..2bd4594c843d 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -62,8 +62,8 @@ def test_agent_tools_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -86,8 +86,8 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=memories_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -106,8 +106,8 @@ def test_agents_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=agents_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -128,8 +128,8 @@ def test_connections_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -148,8 +148,8 @@ def test_files_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -168,8 +168,8 @@ def test_deployments_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -188,8 +188,8 @@ def test_datasets_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -211,6 +211,6 @@ def test_finetuning_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=fine_tuning_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py index fc6fecc417d6..8eccef50195f 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py @@ -50,8 +50,8 @@ async def test_agent_tools_samples_async(self, sample_path: str, **kwargs) -> No await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -75,8 +75,8 @@ async def test_memory_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=memories_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -95,8 +95,8 @@ async def test_agents_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agents_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -117,8 +117,8 @@ async def test_connections_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -139,8 +139,8 @@ async def test_files_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -159,8 +159,8 @@ async def test_deployments_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) @pytest.mark.parametrize( @@ -184,6 +184,6 @@ async def test_datasets_samples(self, sample_path: str, **kwargs) -> None: # Proxy server probably not able to parse the captured print content await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index 9390fdf2d895..bcea91df0eb5 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -19,8 +19,8 @@ evaluationsPreparer = functools.partial( EnvironmentVariableLoader, "", - project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - model_deployment_name="sanitized-model-deployment-name", + azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + azure_ai_model_deployment_name="sanitized-model-deployment-name", azure_ai_agent_name="sanitized-agent-name", ) @@ -184,8 +184,8 @@ def test_evaluation_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) # To run this test with a specific sample, use: @@ -216,8 +216,8 @@ def test_agentic_evaluator_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) # To run this test, use: @@ -247,6 +247,6 @@ def test_generic_agentic_evaluator_sample(self, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["project_endpoint"], - model=kwargs["model_deployment_name"], + project_endpoint=kwargs["azure_ai_project_endpoint"], + model=kwargs["azure_ai_model_deployment_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index afcbe80e2317..a0deb29a2cfd 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -41,8 +41,8 @@ servicePreparer = functools.partial( EnvironmentVariableLoader, "", - project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - model_deployment_name="sanitized-model-deployment-name", + azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + azure_ai_model_deployment_name="sanitized-model-deployment-name", image_generation_model_deployment_name="sanitized-gpt-image", container_app_resource_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/00000/providers/Microsoft.App/containerApps/00000", container_ingress_subdomain_suffix="00000", @@ -77,8 +77,8 @@ fineTuningServicePreparer = functools.partial( EnvironmentVariableLoader, "", - project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - model_deployment_name="sanitized-model-deployment-name", + azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + azure_ai_model_deployment_name="sanitized-model-deployment-name", azure_ai_projects_azure_subscription_id="00000000-0000-0000-0000-000000000000", azure_ai_projects_azure_resource_group="sanitized-resource-group", azure_ai_projects_azure_aoai_account="sanitized-aoai-account", @@ -299,7 +299,7 @@ def open_with_lf( # helper function: create projects client using environment variables def create_client(self, *, operation_group: Optional[str] = None, **kwargs) -> AIProjectClient: # fetch environment variables - endpoint = kwargs.pop("project_endpoint") + endpoint = kwargs.pop("azure_ai_project_endpoint") credential = self.get_credential(AIProjectClient, is_async=False) allow_preview = kwargs.pop("allow_preview", operation_group in {"agents", "tracing"}) @@ -317,7 +317,7 @@ def create_client(self, *, operation_group: Optional[str] = None, **kwargs) -> A # helper function: create async projects client using environment variables def create_async_client(self, *, operation_group: Optional[str] = None, **kwargs) -> AsyncAIProjectClient: # fetch environment variables - endpoint = kwargs.pop("project_endpoint") + endpoint = kwargs.pop("azure_ai_project_endpoint") credential = self.get_credential(AsyncAIProjectClient, is_async=True) allow_preview = kwargs.pop("allow_preview", operation_group in {"agents", "tracing"}) From c357ed8617638b886d43983d28577e5fe8103059 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Tue, 10 Mar 2026 06:25:31 -0700 Subject: [PATCH 08/36] Better Exception messages when you try to use preview features, and "allow_preview=True" is not specified (#45600) --- sdk/ai/azure-ai-projects/assets.json | 2 +- .../ai/projects/aio/operations/_operations.py | 1 + .../ai/projects/aio/operations/_patch.py | 4 + .../aio/operations/_patch_agents_async.py | 176 ++++++++++++++++ .../_patch_evaluation_rules_async.py | 114 ++++++++++ .../azure/ai/projects/operations/_patch.py | 4 + .../ai/projects/operations/_patch_agents.py | 197 ++++++++++++++++++ .../operations/_patch_evaluation_rules.py | 114 ++++++++++ .../test_agent_create_version_exception.py | 51 +++++ ...st_agent_create_version_exception_async.py | 52 +++++ sdk/ai/azure-ai-projects/tests/test_base.py | 8 +- 11 files changed, 716 insertions(+), 7 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py create mode 100644 sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py create mode 100644 sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception_async.py diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 1d1c8afa0323..5d326c8c0af9 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_e907ded382" + "Tag": "python/ai/azure-ai-projects_a7df068ea3" } diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 6f2e077a1e91..3499ba5338e5 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -115,6 +115,7 @@ _SERIALIZER = Serializer() _SERIALIZER.client_side_validation = False + class BetaOperations: """ .. warning:: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index 3d103d797c20..fd4e68774f3b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -9,7 +9,9 @@ """ from typing import Any, List +from ._patch_agents_async import AgentsOperations from ._patch_datasets_async import DatasetsOperations +from ._patch_evaluation_rules_async import EvaluationRulesOperations from ._patch_telemetry_async import TelemetryOperations from ._patch_connections_async import ConnectionsOperations from ._patch_memories_async import BetaMemoryStoresOperations @@ -53,7 +55,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: __all__: List[str] = [ + "AgentsOperations", "BetaEvaluationTaxonomiesOperations", + "EvaluationRulesOperations", "BetaEvaluatorsOperations", "BetaInsightsOperations", "BetaMemoryStoresOperations", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py new file mode 100644 index 000000000000..0ed177f12ab9 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py @@ -0,0 +1,176 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Optional, Any, IO, overload +from azure.core.exceptions import HttpResponseError +from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset +from ... import models as _models +from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE + + +class AgentsOperations(GeneratedAgentsOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`agents` attribute. + """ + + @overload + async def create_version( + self, + agent_name: str, + *, + definition: _models.AgentDefinition, + content_type: str = "application/json", + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_version( + self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_version( + self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + async def create_version( + self, + agent_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + definition: _models.AgentDefinition = _Unset, + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + try: + return await super().create_version( + agent_name, + body, + definition=definition, + metadata=metadata, + description=description, + **kwargs, + ) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py new file mode 100644 index 000000000000..08ab156a9bbe --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py @@ -0,0 +1,114 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Any, IO, overload +from azure.core.exceptions import HttpResponseError +from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON +from ... import models as _models +from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE + + +class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`evaluation_rules` attribute. + """ + + @overload + async def create_or_update( + self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_or_update( + self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_or_update( + self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + async def create_or_update( + self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Is one of the following types: + EvaluationRule, JSON, IO[bytes] Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + try: + return await super().create_or_update(id, evaluation_rule, **kwargs) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index 5f66db634e9d..bc78f4d6baf8 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -9,7 +9,9 @@ """ from typing import Any, List +from ._patch_agents import AgentsOperations from ._patch_datasets import DatasetsOperations +from ._patch_evaluation_rules import EvaluationRulesOperations from ._patch_telemetry import TelemetryOperations from ._patch_connections import ConnectionsOperations from ._patch_memories import BetaMemoryStoresOperations @@ -53,7 +55,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: __all__: List[str] = [ + "AgentsOperations", "BetaEvaluationTaxonomiesOperations", + "EvaluationRulesOperations", "BetaEvaluatorsOperations", "BetaInsightsOperations", "BetaMemoryStoresOperations", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py new file mode 100644 index 000000000000..6f2f3374d3f9 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py @@ -0,0 +1,197 @@ +# pylint: disable=line-too-long,useless-suppression,pointless-string-statement +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Optional, Any, IO, overload, Final +from azure.core.exceptions import HttpResponseError +from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset +from .. import models as _models + +""" +Example service response payload when the caller is trying to use a feature preview without opt-in flag (service error 403 (Forbidden)): + +"error": { + "code": "preview_feature_required", + "message": "Workflow agents is in preview. This operation requires the following opt-in preview feature(s): WorkflowAgents=V1Preview. Include the 'Foundry-Features: WorkflowAgents=V1Preview' header in your request.", + "param": "Foundry-Features", + "type": "invalid_request_error", + "details": [], + "additionalInfo": { + "request_id": "fdbc95804b7599404973026cd9ec732a" + } + } + +""" +_PREVIEW_FEATURE_REQUIRED_CODE: Final = "preview_feature_required" +_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE: Final = ( + '\n**Python SDK users**: This operation requires you to set "allow_preview=True" ' + "when calling the AIProjectClient constructor. " + "\nNote that preview features are under development and subject to change." +) + + +class AgentsOperations(GeneratedAgentsOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`agents` attribute. + """ + + @overload + def create_version( + self, + agent_name: str, + *, + definition: _models.AgentDefinition, + content_type: str = "application/json", + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_version( + self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_version( + self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + def create_version( + self, + agent_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + definition: _models.AgentDefinition = _Unset, + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + try: + return super().create_version( + agent_name, + body, + definition=definition, + metadata=metadata, + description=description, + **kwargs, + ) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py new file mode 100644 index 000000000000..5c2ca412a468 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py @@ -0,0 +1,114 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Any, IO, overload +from azure.core.exceptions import HttpResponseError +from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON +from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE +from .. import models as _models + + +class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`evaluation_rules` attribute. + """ + + @overload + def create_or_update( + self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_or_update( + self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_or_update( + self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + def create_or_update( + self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Is one of the following types: + EvaluationRule, JSON, IO[bytes] Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + try: + return super().create_or_update(id, evaluation_rule, **kwargs) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py new file mode 100644 index 000000000000..6cc81e4f5553 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py @@ -0,0 +1,51 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +# cSpell:disable + +import functools +import pytest +from test_base import TestBase, servicePreparer +from devtools_testutils import recorded_by_proxy, RecordedTransport +from azure.core.exceptions import HttpResponseError +from azure.ai.projects.models import WorkflowAgentDefinition +from azure.ai.projects.operations._patch_agents import _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE + +# Minimal workflow YAML — the service rejects the request before validating the +# definition, so the content only needs to be a non-empty string. +_MINIMAL_WORKFLOW_YAML = """\ +kind: workflow +trigger: + kind: OnConversationStart + id: my_workflow + actions: [] +""" + + +# To run this test: +# pytest tests\agents\test_agent_create_version_exception.py -s +class TestAgentCreateVersionException(TestBase): + + @servicePreparer() + @recorded_by_proxy(RecordedTransport.AZURE_CORE) + def test_create_version_raises_exception_when_allow_preview_not_set(self, **kwargs): + """ + Verify that calling agents.create_version() with a WorkflowAgentDefinition when + AIProjectClient was constructed WITHOUT allow_preview=True raises an HttpResponseError + (HTTP 403) whose message contains the SDK-specific hint pointing users to set + allow_preview=True. + """ + # Deliberately create client WITHOUT allow_preview=True + project_client = self.create_client(**kwargs) + + with pytest.raises(HttpResponseError) as exc_info: + project_client.agents.create_version( + agent_name="workflow-agent-preview-test", + definition=WorkflowAgentDefinition(workflow=_MINIMAL_WORKFLOW_YAML), + ) + + raised = exc_info.value + assert raised.status_code == 403 + assert _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE in raised.message diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception_async.py new file mode 100644 index 000000000000..a4ebac76ffa6 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception_async.py @@ -0,0 +1,52 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +# cSpell:disable + +import pytest +from test_base import TestBase, servicePreparer +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from azure.core.exceptions import HttpResponseError +from azure.ai.projects.models import WorkflowAgentDefinition +from azure.ai.projects.operations._patch_agents import _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE + +# Minimal workflow YAML — the service rejects the request before validating the +# definition, so the content only needs to be a non-empty string. +_MINIMAL_WORKFLOW_YAML = """\ +kind: workflow +trigger: + kind: OnConversationStart + id: my_workflow + actions: [] +""" + + +# To run this test: +# pytest tests\agents\test_agent_create_version_exception_async.py -s +class TestAgentCreateVersionExceptionAsync(TestBase): + + @servicePreparer() + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE) + async def test_create_version_raises_exception_when_allow_preview_not_set_async(self, **kwargs): + """ + Verify that calling agents.create_version() with a WorkflowAgentDefinition when + AsyncAIProjectClient was constructed WITHOUT allow_preview=True raises an HttpResponseError + (HTTP 403) whose message contains the SDK-specific hint pointing users to set + allow_preview=True. + """ + # Deliberately create client WITHOUT allow_preview=True + project_client = self.create_async_client(**kwargs) + + async with project_client: + with pytest.raises(HttpResponseError) as exc_info: + await project_client.agents.create_version( + agent_name="workflow-agent-preview-test", + definition=WorkflowAgentDefinition(workflow=_MINIMAL_WORKFLOW_YAML), + ) + + raised = exc_info.value + assert raised.status_code == 403 + assert _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE in raised.message diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index a0deb29a2cfd..ac072e7f5844 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -44,8 +44,6 @@ azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", azure_ai_model_deployment_name="sanitized-model-deployment-name", image_generation_model_deployment_name="sanitized-gpt-image", - container_app_resource_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/00000/providers/Microsoft.App/containerApps/00000", - container_ingress_subdomain_suffix="00000", bing_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-bing-connection", ai_search_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-ai-search-connection", bing_custom_search_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-bing-custom-search-connection", @@ -297,11 +295,10 @@ def open_with_lf( return patched_open_crlf_to_lf(file, mode, buffering, encoding, errors, newline, closefd, opener) # helper function: create projects client using environment variables - def create_client(self, *, operation_group: Optional[str] = None, **kwargs) -> AIProjectClient: + def create_client(self, *, allow_preview: bool = False, **kwargs) -> AIProjectClient: # fetch environment variables endpoint = kwargs.pop("azure_ai_project_endpoint") credential = self.get_credential(AIProjectClient, is_async=False) - allow_preview = kwargs.pop("allow_preview", operation_group in {"agents", "tracing"}) print(f"Creating AIProjectClient with endpoint: {endpoint}") @@ -315,11 +312,10 @@ def create_client(self, *, operation_group: Optional[str] = None, **kwargs) -> A return client # helper function: create async projects client using environment variables - def create_async_client(self, *, operation_group: Optional[str] = None, **kwargs) -> AsyncAIProjectClient: + def create_async_client(self, *, allow_preview: bool = False, **kwargs) -> AsyncAIProjectClient: # fetch environment variables endpoint = kwargs.pop("azure_ai_project_endpoint") credential = self.get_credential(AsyncAIProjectClient, is_async=True) - allow_preview = kwargs.pop("allow_preview", operation_group in {"agents", "tracing"}) print(f"Creating AsyncAIProjectClient with endpoint: {endpoint}") From 7602ddb547e7095e4db5b1b12c8a59bc61ce455c Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Tue, 10 Mar 2026 20:57:07 +0530 Subject: [PATCH 09/36] marking finetuning pause and resume operations as live extended tests (#45611) * marking finetuning pause and resume operations as live extended tests * updating recording --------- Co-authored-by: Jayesh Tanna --- sdk/ai/azure-ai-projects/assets.json | 2 +- .../azure-ai-projects/tests/finetuning/test_finetuning.py | 8 ++++++++ .../tests/finetuning/test_finetuning_async.py | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 5d326c8c0af9..3d798e3359e9 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_a7df068ea3" + "Tag": "python/ai/azure-ai-projects_f6ff7973d2" } diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index d8e28452557f..aeb407b0e5f0 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -653,6 +653,10 @@ def test_finetuning_list_events(self, **kwargs): self._cleanup_test_file(openai_client, train_file.id) self._cleanup_test_file(openai_client, validation_file.id) + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", + ) @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_finetuning_pause_job(self, **kwargs): @@ -683,6 +687,10 @@ def test_finetuning_pause_job(self, **kwargs): print(f"[test_finetuning_pause_job] Successfully paused and verified job: {running_job_id}") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", + ) @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_finetuning_resume_job(self, **kwargs): diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index be0cc2de95dc..b9789e077e92 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -679,6 +679,10 @@ async def test_finetuning_list_events_async(self, **kwargs): await self._cleanup_test_file_async(openai_client, train_file.id) await self._cleanup_test_file_async(openai_client, validation_file.id) + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", + ) @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_finetuning_pause_job_async(self, **kwargs): @@ -710,6 +714,10 @@ async def test_finetuning_pause_job_async(self, **kwargs): print(f"[test_finetuning_pause_job] Job status after pause: {paused_job.status}") print(f"[test_finetuning_pause_job] Successfully paused and verified job: {running_job_id}") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", + ) @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_finetuning_resume_job_async(self, **kwargs): From c2e7e579920942fbd9efedb2ea656cc8d867b749 Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Tue, 10 Mar 2026 15:27:51 -0700 Subject: [PATCH 10/36] Rename env vars (#45599) * rename env vars * rename env var * resolved comments * remove chat completion * resolved comment --- sdk/ai/azure-ai-projects/.env.template | 6 +- sdk/ai/azure-ai-projects/CHANGELOG.md | 4 +- sdk/ai/azure-ai-projects/README.md | 20 ++--- .../samples/agents/sample_agent_basic.py | 8 +- .../agents/sample_agent_basic_async.py | 8 +- .../agents/sample_agent_retrieve_basic.py | 8 +- .../sample_agent_retrieve_basic_async.py | 8 +- .../agents/sample_agent_stream_events.py | 8 +- .../agents/sample_agent_structured_output.py | 8 +- .../sample_agent_structured_output_async.py | 8 +- .../agents/sample_workflow_multi_agent.py | 10 +-- .../sample_workflow_multi_agent_async.py | 10 +-- ..._agent_basic_with_azure_monitor_tracing.py | 8 +- ...sample_agent_basic_with_console_tracing.py | 8 +- ..._with_console_tracing_custom_attributes.py | 8 +- .../agents/tools/sample_agent_ai_search.py | 8 +- .../tools/sample_agent_azure_function.py | 8 +- .../tools/sample_agent_bing_custom_search.py | 8 +- .../tools/sample_agent_bing_grounding.py | 8 +- .../tools/sample_agent_browser_automation.py | 8 +- .../tools/sample_agent_code_interpreter.py | 8 +- .../sample_agent_code_interpreter_async.py | 8 +- ...ample_agent_code_interpreter_with_files.py | 8 +- ...agent_code_interpreter_with_files_async.py | 8 +- .../agents/tools/sample_agent_computer_use.py | 4 +- .../tools/sample_agent_computer_use_async.py | 4 +- .../agents/tools/sample_agent_fabric.py | 8 +- .../agents/tools/sample_agent_file_search.py | 8 +- .../sample_agent_file_search_in_stream.py | 8 +- ...ample_agent_file_search_in_stream_async.py | 8 +- .../tools/sample_agent_function_tool.py | 8 +- .../tools/sample_agent_function_tool_async.py | 8 +- .../tools/sample_agent_image_generation.py | 8 +- .../sample_agent_image_generation_async.py | 8 +- .../samples/agents/tools/sample_agent_mcp.py | 8 +- .../agents/tools/sample_agent_mcp_async.py | 8 +- ...ample_agent_mcp_with_project_connection.py | 8 +- ...agent_mcp_with_project_connection_async.py | 8 +- .../tools/sample_agent_memory_search.py | 8 +- .../tools/sample_agent_memory_search_async.py | 8 +- .../agents/tools/sample_agent_openapi.py | 8 +- ...e_agent_openapi_with_project_connection.py | 8 +- .../agents/tools/sample_agent_sharepoint.py | 8 +- .../agents/tools/sample_agent_to_agent.py | 8 +- .../agents/tools/sample_agent_web_search.py | 8 +- .../tools/sample_agent_web_search_preview.py | 8 +- ...ple_agent_web_search_with_custom_search.py | 8 +- .../samples/connections/sample_connections.py | 4 +- .../connections/sample_connections_async.py | 4 +- .../samples/datasets/sample_datasets.py | 4 +- .../samples/datasets/sample_datasets_async.py | 4 +- .../datasets/sample_datasets_download.py | 4 +- .../samples/deployments/sample_deployments.py | 8 +- .../deployments/sample_deployments_async.py | 8 +- .../samples/evaluations/README.md | 8 +- .../agentic_evaluators/sample_coherence.py | 8 +- .../agentic_evaluators/sample_fluency.py | 8 +- .../agent_utils.py | 2 +- .../sample_generic_agentic_evaluator.py | 6 +- .../agentic_evaluators/sample_groundedness.py | 8 +- .../sample_intent_resolution.py | 8 +- .../agentic_evaluators/sample_relevance.py | 8 +- .../sample_response_completeness.py | 8 +- .../sample_task_adherence.py | 8 +- .../sample_task_completion.py | 8 +- .../sample_task_navigation_efficiency.py | 4 +- .../sample_tool_call_accuracy.py | 8 +- .../sample_tool_call_success.py | 8 +- .../sample_tool_input_accuracy.py | 8 +- .../sample_tool_output_utilization.py | 8 +- .../sample_tool_selection.py | 8 +- .../evaluations/sample_agent_evaluation.py | 12 +-- .../sample_agent_response_evaluation.py | 12 +-- ..._response_evaluation_with_function_tool.py | 8 +- .../sample_continuous_evaluation_rule.py | 12 +-- .../evaluations/sample_eval_catalog.py | 4 +- ...mple_eval_catalog_code_based_evaluators.py | 8 +- ...le_eval_catalog_prompt_based_evaluators.py | 8 +- .../sample_evaluation_cluster_insight.py | 10 +-- .../sample_evaluation_compare_insight.py | 8 +- .../sample_evaluations_ai_assisted.py | 8 +- ...ple_evaluations_builtin_with_dataset_id.py | 8 +- ...le_evaluations_builtin_with_inline_data.py | 8 +- ...valuations_builtin_with_inline_data_oai.py | 8 +- .../sample_evaluations_builtin_with_traces.py | 8 +- .../evaluations/sample_evaluations_graders.py | 8 +- ...aluations_score_model_grader_with_image.py | 8 +- .../evaluations/sample_model_evaluation.py | 8 +- .../evaluations/sample_redteam_evaluations.py | 10 +-- .../sample_scheduled_evaluations.py | 16 ++-- .../samples/files/sample_files.py | 4 +- .../samples/files/sample_files_async.py | 4 +- .../finetuning/sample_finetuning_dpo_job.py | 4 +- .../sample_finetuning_dpo_job_async.py | 4 +- ...le_finetuning_oss_models_supervised_job.py | 4 +- ...etuning_oss_models_supervised_job_async.py | 4 +- .../sample_finetuning_reinforcement_job.py | 4 +- ...mple_finetuning_reinforcement_job_async.py | 4 +- .../sample_finetuning_supervised_job.py | 4 +- .../sample_finetuning_supervised_job_async.py | 4 +- .../samples/indexes/sample_indexes.py | 4 +- .../samples/indexes/sample_indexes_async.py | 4 +- .../mcp_client/sample_mcp_tool_async.py | 6 +- .../memories/sample_memory_advanced.py | 4 +- .../memories/sample_memory_advanced_async.py | 4 +- .../samples/memories/sample_memory_basic.py | 4 +- .../memories/sample_memory_basic_async.py | 4 +- .../samples/memories/sample_memory_crud.py | 4 +- .../memories/sample_memory_crud_async.py | 4 +- .../samples/red_team/sample_red_team.py | 8 +- .../samples/red_team/sample_red_team_async.py | 8 +- .../responses/sample_responses_basic.py | 10 +-- .../responses/sample_responses_basic_async.py | 10 +-- ...responses_basic_without_aiprojectclient.py | 8 +- ...ses_basic_without_aiprojectclient_async.py | 8 +- .../responses/sample_responses_image_input.py | 8 +- .../sample_responses_stream_events.py | 8 +- .../sample_responses_stream_manager.py | 8 +- .../sample_responses_structured_output.py | 8 +- .../samples/telemetry/sample_telemetry.py | 4 +- .../telemetry/sample_telemetry_async.py | 4 +- .../telemetry/test_ai_agents_instrumentor.py | 8 +- .../test_ai_agents_instrumentor_async.py | 8 +- .../telemetry/test_responses_instrumentor.py | 84 +++++++++---------- .../test_responses_instrumentor_async.py | 72 ++++++++-------- ...sponses_instrumentor_browser_automation.py | 8 +- ...s_instrumentor_browser_automation_async.py | 8 +- ...responses_instrumentor_code_interpreter.py | 8 +- ...ses_instrumentor_code_interpreter_async.py | 8 +- ...test_responses_instrumentor_file_search.py | 8 +- ...esponses_instrumentor_file_search_async.py | 8 +- .../test_responses_instrumentor_mcp.py | 8 +- .../test_responses_instrumentor_mcp_async.py | 8 +- .../test_responses_instrumentor_metrics.py | 2 +- .../test_responses_instrumentor_workflow.py | 8 +- ...t_responses_instrumentor_workflow_async.py | 8 +- .../tests/agents/test_agent_responses_crud.py | 4 +- .../agents/test_agent_responses_crud_async.py | 4 +- .../tests/agents/test_agents_crud.py | 2 +- .../tests/agents/test_agents_crud_async.py | 2 +- ...est_agent_code_interpreter_and_function.py | 4 +- ..._agent_file_search_and_code_interpreter.py | 4 +- .../test_agent_file_search_and_function.py | 8 +- ...t_file_search_code_interpreter_function.py | 2 +- .../test_multitool_with_conversations.py | 2 +- .../agents/tools/test_agent_ai_search.py | 2 +- .../tools/test_agent_ai_search_async.py | 2 +- .../agents/tools/test_agent_bing_grounding.py | 4 +- .../tools/test_agent_code_interpreter.py | 4 +- .../test_agent_code_interpreter_async.py | 2 +- .../agents/tools/test_agent_file_search.py | 4 +- .../tools/test_agent_file_search_async.py | 4 +- .../tools/test_agent_file_search_stream.py | 2 +- .../test_agent_file_search_stream_async.py | 2 +- .../agents/tools/test_agent_function_tool.py | 6 +- .../tools/test_agent_function_tool_async.py | 6 +- .../tools/test_agent_image_generation.py | 2 +- .../test_agent_image_generation_async.py | 2 +- .../tests/agents/tools/test_agent_mcp.py | 4 +- .../agents/tools/test_agent_mcp_async.py | 2 +- .../agents/tools/test_agent_memory_search.py | 2 +- .../tools/test_agent_memory_search_async.py | 2 +- .../tests/agents/tools/test_agent_openapi.py | 2 +- .../agents/tools/test_agent_openapi_async.py | 2 +- .../test_agent_tools_with_conversations.py | 8 +- .../agents/tools/test_agent_web_search.py | 2 +- .../tools/test_agent_web_search_async.py | 2 +- .../tests/datasets/test_datasets.py | 2 +- .../tests/datasets/test_datasets_async.py | 2 +- .../tests/deployments/test_deployments.py | 4 +- .../deployments/test_deployments_async.py | 4 +- .../tests/finetuning/test_finetuning.py | 4 +- .../tests/finetuning/test_finetuning_async.py | 4 +- .../tests/responses/test_responses.py | 2 +- .../tests/responses/test_responses_async.py | 2 +- .../azure-ai-projects/tests/samples/README.md | 12 +-- .../tests/samples/test_samples.py | 32 +++---- .../tests/samples/test_samples_async.py | 28 +++---- .../tests/samples/test_samples_evaluations.py | 18 ++-- sdk/ai/azure-ai-projects/tests/test_base.py | 12 +-- 180 files changed, 681 insertions(+), 679 deletions(-) diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index df66f2a71199..620367d29578 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -20,9 +20,9 @@ AZURE_AI_PROJECTS_CONSOLE_LOGGING= # Project endpoint has the format: # `https://.services.ai.azure.com/api/projects/` -AZURE_AI_PROJECT_ENDPOINT= -AZURE_AI_MODEL_DEPLOYMENT_NAME= -AZURE_AI_AGENT_NAME= +FOUNDRY_PROJECT_ENDPOINT= +FOUNDRY_MODEL_NAME= +FOUNDRY_AGENT_NAME= CONVERSATION_ID= CONNECTION_NAME= MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME= diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 34c72e0a6fa5..44dfff20303b 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -16,7 +16,9 @@ ### Sample updates -* Placeholder +* Renamed environment variable `AZURE_AI_PROJECT_ENDPOINT` to `FOUNDRY_PROJECT_ENDPOINT` in all samples. +* Renamed environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` to `FOUNDRY_MODEL_NAME` in all samples. +* Renamed environment variable `AZURE_AI_MODEL_AGENT_NAME` to `FOUNDRY_AGENT_NAME` in all samples. ### Other Changes diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 8c741a297add..15e6b44c62ef 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -54,7 +54,7 @@ To report an issue with the client library, or request additional features, plea * Python 3.9 or later. * An [Azure subscription][azure_sub]. * A [project in Microsoft Foundry](https://learn.microsoft.com/azure/foundry/how-to/create-projects). -* A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `AZURE_AI_PROJECT_ENDPOINT` was defined to hold this value. +* A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `FOUNDRY_PROJECT_ENDPOINT` was defined to hold this value. * An Entra ID token for authentication. Your application needs an object that implements the [TokenCredential](https://learn.microsoft.com/python/api/azure-core/azure.core.credentials.tokencredential) interface. Code samples here use [DefaultAzureCredential](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential). To get that working, you will need: * An appropriate role assignment. See [Role-based access control in Microsoft Foundry portal](https://learn.microsoft.com/azure/foundry/concepts/rbac-foundry). Role assignment can be done via the "Access Control (IAM)" tab of your Azure AI Project resource in the Azure portal. * [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed. @@ -87,7 +87,7 @@ from azure.identity import DefaultAzureCredential with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential) as project_client, ): ``` @@ -107,7 +107,7 @@ from azure.identity.aio import DefaultAzureCredential async with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential) as project_client, ): ``` @@ -117,20 +117,20 @@ async with ( Your Microsoft Foundry project may have one or more AI models deployed. These could be OpenAI models, Microsoft models, or models from other providers. Use the code below to get an authenticated [OpenAI](https://github.com/openai/openai-python?tab=readme-ov-file#usage) client from the [openai](https://pypi.org/project/openai/) package, and execute an example multi-turn "Responses" calls. -The code below assumes the environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). +The code below assumes the environment variable `FOUNDRY_MODEL_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). ```python with project_client.get_openai_client() as openai_client: response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) @@ -145,7 +145,7 @@ See the "responses" folder in the [package samples][samples] for additional samp The `.agents` property on the `AIProjectClient` gives you access to all Agent operations. Agents use an extension of the OpenAI Responses protocol, so you will need to get an `OpenAI` client to do Agent operations, as shown in the example below. -The code below assumes environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). +The code below assumes environment variable `FOUNDRY_MODEL_NAME` is defined. It's the deployment name of an AI model in your Foundry Project. See "Build" menu, under "Models" (First column of the "Deployments" table). See the "agents" folder in the [package samples][samples] for an extensive set of samples, including streaming, tool usage and memory store usage. @@ -156,7 +156,7 @@ with project_client.get_openai_client() as openai_client: agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) @@ -765,7 +765,7 @@ with ( project_client.get_openai_client() as openai_client, ): agent = project_client.agents.create_version( - agent_name=os.environ["AZURE_AI_AGENT_NAME"], + agent_name=os.environ["FOUNDRY_AGENT_NAME"], definition=PromptAgentDefinition( model=model_deployment_name, instructions="You are a helpful assistant that answers general questions", @@ -1357,7 +1357,7 @@ By default logs redact the values of URL query strings, the values of some HTTP ```python project_client = AIProjectClient( credential=DefaultAzureCredential(), - endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], + endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], logging_enable=True ) ``` diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py index 1b57d0bcd29c..e14dbbcb6148 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py index 69404d31ae35..009171acdee9 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main() -> None: @@ -48,7 +48,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions.", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py index 876dc8daebcd..07d039779a94 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py @@ -22,9 +22,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -36,8 +36,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model = os.environ["FOUNDRY_MODEL_NAME"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py index 8baa7034c139..3f2c79647935 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py @@ -22,9 +22,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -37,8 +37,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model = os.environ["FOUNDRY_MODEL_NAME"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py index 5ab97c36b587..990fb833b7e4 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py index dfeb5c961720..b7bd9155561b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py @@ -24,9 +24,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv pydantic Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -51,7 +51,7 @@ class CalendarEvent(BaseModel): participants: list[str] -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -62,7 +62,7 @@ class CalendarEvent(BaseModel): agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], text=PromptAgentDefinitionTextOptions( format=TextResponseFormatJsonSchema(name="CalendarEvent", schema=CalendarEvent.model_json_schema()) ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py index 12bdaf31231a..48b58d57633c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py @@ -24,9 +24,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp pydantic Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] class CalendarEvent(BaseModel): @@ -63,7 +63,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], text=PromptAgentDefinitionTextOptions( format=TextResponseFormatJsonSchema(name="CalendarEvent", schema=CalendarEvent.model_json_schema()) ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py index b8674dc5c146..6f3d21766939 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -46,7 +46,7 @@ teacher_agent = project_client.agents.create_version( agent_name="teacher-agent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", @@ -58,7 +58,7 @@ student_agent = project_client.agents.create_version( agent_name="student-agent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py index b565a04be074..1d971d0ce3b3 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -36,7 +36,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main(): @@ -50,7 +50,7 @@ async def main(): teacher_agent = await project_client.agents.create_version( agent_name="teacher-agent-async", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", @@ -61,7 +61,7 @@ async def main(): student_agent = await project_client.agents.create_version( agent_name="student-agent-async", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py index 0f318459182f..a9cd0f9907b2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_azure_monitor_tracing.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-monitor-opentelemetry Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -46,7 +46,7 @@ with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential) as project_client, ): # [START setup_azure_monitor_tracing] # Enable Azure Monitor tracing @@ -62,7 +62,7 @@ # [END create_span_for_scenario] with project_client.get_openai_client() as openai_client: agent_definition = PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py index e5aa0582e9a6..82d04285785b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv opentelemetry-sdk azure-core-tracing-opentelemetry Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -90,11 +90,11 @@ def display_conversation_item(item: Any) -> None: # [END create_span_for_scenario] with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential) as project_client, project_client.get_openai_client() as openai_client, ): agent_definition = PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py index 64786e3a00e0..7fe16305c996 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv opentelemetry-sdk azure-core-tracing-opentelemetry Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING - Set to `true` to enable GenAI telemetry tracing, which is disabled by default. @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] # Define the custom span processor that is used for adding the custom @@ -94,7 +94,7 @@ def on_end(self, span: ReadableSpan): ): agent_definition = PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py index 6f082269687a..75ba32b79d8b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) AI_SEARCH_PROJECT_CONNECTION_ID - The AI Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -66,7 +66,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a helpful assistant. You must always provide citations for answers using the tool and render them as: `\u3010message_idx:search_idx\u2020source\u3011`.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py index 242ee742229b..5e78c32d6325 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_azure_function.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0b1" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) STORAGE_INPUT_QUEUE_NAME - The name of the Azure Storage Queue to use for input and output in the Azure Function tool. 4) STORAGE_OUTPUT_QUEUE_NAME - The name of the Azure Storage Queue to use for output in the Azure Function tool. @@ -44,7 +44,7 @@ agent = None -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -82,7 +82,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py index 1ebf4c6d213b..b61867cbe8f2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py @@ -27,9 +27,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID - The Bing Custom Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -50,7 +50,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -74,7 +74,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a helpful agent that can use Bing Custom Search tools to assist users. Use the available Bing Custom Search tools to answer questions and perform tasks.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py index 22fb479f0109..4adb31cec416 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py @@ -35,9 +35,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_PROJECT_CONNECTION_ID - The Bing project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. """ @@ -55,7 +55,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -76,7 +76,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py index aa660411a6cc..2c0b98a96a5c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BROWSER_AUTOMATION_PROJECT_CONNECTION_ID - The browser automation project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are an Agent helping with browser automation tasks. You can answer questions, provide information, and assist with various tasks related to web browsing using the Browser Automation tool available to you.""", diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py index 6007468f8439..e2c64efb44be 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -47,7 +47,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py index edde775f47aa..5371a781e75a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main() -> None: @@ -46,7 +46,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[CodeInterpreterTool()], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py index 3cb511cfcc3b..336fc0c17944 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -57,7 +57,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py index 69ddd048bcc8..88b59c545e49 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main() -> None: @@ -59,7 +59,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=[file.id]))], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py index 3b1ec849257c..bf448cee686a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py @@ -23,7 +23,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) (Optional) COMPUTER_USE_MODEL_DEPLOYMENT_NAME - The deployment name of the computer-use-preview model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -45,7 +45,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py index c2c85a6c9906..2720916e1759 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py @@ -23,7 +23,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) (Optional) COMPUTER_USE_MODEL_DEPLOYMENT_NAME - The deployment name of the computer-use-preview model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -44,7 +44,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py index 20bcd70f597d..45a2e29ee4ed 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) FABRIC_PROJECT_CONNECTION_ID - The Fabric project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -59,7 +59,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py index 9e14fdd34461..c785e1877ade 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can search through product information.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py index 4bac22ce828c..682176ad690c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -63,7 +63,7 @@ agent = project_client.agents.create_version( agent_name="StreamingFileSearchAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can search through product information and provide detailed responses. Use the file search tool to find relevant information before answering.", tools=[FileSearchTool(vector_store_ids=[vector_store.id])], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py index 7f02ea13a7d6..8a2fdd22bfc6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main() -> None: @@ -63,7 +63,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="StreamingFileSearchAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can search through product information and provide detailed responses. Use the file search tool to find relevant information before answering.", tools=[FileSearchTool(vector_store_ids=[vector_store.id])], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py index 701201d55d99..33166f2ad06f 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ def get_horoscope(sign: str) -> str: return f"{sign}: Next Tuesday you will befriend a baby otter." -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -69,7 +69,7 @@ def get_horoscope(sign: str) -> str: agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can use function tools.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py index ccbc373e6585..1ecd17dee095 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def get_horoscope(sign: str) -> str: @@ -70,7 +70,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can use function tools.", tools=[func_tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py index 5b9ea7011b3c..5232a443ca34 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation.py @@ -28,9 +28,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) + 2) FOUNDRY_MODEL_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) used by the Agent for understanding and responding to prompts. This is NOT the image generation model. 3) IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME - The deployment name of the image generation model (e.g. gpt-image-1) used by the ImageGenTool. @@ -53,7 +53,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -73,7 +73,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="Generate images based on user prompts", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py index b7859d6483eb..37fa57e82a16 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_image_generation_async.py @@ -28,9 +28,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) + 2) FOUNDRY_MODEL_NAME - The deployment name of the chat model (e.g., gpt-4o, gpt-4o-mini, gpt-5o, gpt-5o-mini) used by the Agent for understanding and responding to prompts. This is NOT the image generation model. 3) IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME - The deployment name of the image generation model (e.g. gpt-image-1) used by the ImageGenTool. @@ -53,7 +53,7 @@ from azure.ai.projects.models import PromptAgentDefinition, ImageGenTool load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main(): @@ -68,7 +68,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="Generate images based on user prompts", tools=[ImageGenTool(model=image_generation_model, quality="low", size="1024x1024")], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py index c318f9004f1e..9b86f113f234 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.", tools=[mcp_tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py index cc12b02a8fdd..917caae9da33 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main(): @@ -55,7 +55,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful agent that can use MCP tools to assist users. Use the available MCP tools to answer questions and perform tasks.", tools=tools, ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py index be1036a9fde8..d34313d8651d 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys with key equals to "Authorization" and value to be "Bearer ". @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -55,7 +55,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent7", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="Use MCP tools as needed", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py index 71ec422987a4..974699e42901 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MCP_PROJECT_CONNECTION_ID - The connection resource ID in Custom keys with key equals to "Authorization" and value to be "Bearer ". @@ -35,7 +35,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main(): @@ -58,7 +58,7 @@ async def main(): agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="Use MCP tools as needed", tools=tools, ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py index 26f9221631f1..d9576a243c2d 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search.py @@ -23,9 +23,9 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + 2) FOUNDRY_MODEL_NAME - The deployment name of the Agent's AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -48,7 +48,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -96,7 +96,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py index 6e959c0c3a88..72fa11db61d7 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_memory_search_async.py @@ -23,9 +23,9 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the Agent's AI model, + 2) FOUNDRY_MODEL_NAME - The deployment name of the Agent's AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model for memory, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -51,7 +51,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, @@ -90,7 +90,7 @@ async def main() -> None: agent = await project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", tools=[ MemorySearchPreviewTool( diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py index aba7820c69d6..5e45e9ec41bf 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv jsonref Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -38,7 +38,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -65,7 +65,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py index 886288df69de..b580ee51eb2a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv jsonref Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) OPENAPI_PROJECT_CONNECTION_ID - The OpenAPI project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -42,7 +42,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -75,7 +75,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py index 6a108dfe83a9..6af21a566a6c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) SHAREPOINT_PROJECT_CONNECTION_ID - The SharePoint project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -59,7 +59,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="""You are a helpful agent that can use SharePoint tools to assist users. Use the available SharePoint tools to answer questions and perform tasks.""", tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py index 133b99d589e0..97ae4365f075 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) A2A_PROJECT_CONNECTION_ID - The A2A project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -61,7 +61,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant.", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py index 01729d031854..5eea41125bf8 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py @@ -26,9 +26,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -46,7 +46,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -60,7 +60,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can search the web", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py index bf09f23786ff..4148bb7fd29e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py @@ -26,9 +26,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,7 +42,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -56,7 +56,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent105", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can search the web", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py index 41d8d6f75aa2..b9da3e2e856b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py @@ -27,9 +27,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. 3) BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID - The Bing Custom Search project connection ID, as found in the "Connections" tab in your Microsoft Foundry project. @@ -51,7 +51,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -70,7 +70,7 @@ agent = project_client.agents.create_version( agent_name="MyAgent", definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that can search the web and bing", tools=[tool], ), diff --git a/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py b/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py index a867acc2106e..1de80cfdd689 100644 --- a/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py +++ b/sdk/ai/azure-ai-projects/samples/connections/sample_connections.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - The name of a connection, as found in the "Connected resources" tab in the Management Center of your Microsoft Foundry project. @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] connection_name = os.environ["CONNECTION_NAME"] with ( diff --git a/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py b/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py index e5814abf27fc..f23c5f932fbe 100644 --- a/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py +++ b/sdk/ai/azure-ai-projects/samples/connections/sample_connections_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - The name of a connection, as found in the "Connected resources" tab in the Management Center of your Microsoft Foundry project. @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] connection_name = os.environ["CONNECTION_NAME"] diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py index eb664060a12c..791b33754479 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -36,7 +36,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version_1 = os.environ.get("DATASET_VERSION_1", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py index 7740507124d9..6ceacc201426 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_async.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -45,7 +45,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version_1 = os.environ.get("DATASET_VERSION_1", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py index 2a9aa4fc73a2..a32d6b1e8368 100644 --- a/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py +++ b/sdk/ai/azure-ai-projects/samples/datasets/sample_datasets_download.py @@ -19,7 +19,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) CONNECTION_NAME - Optional. The name of the Azure Storage Account connection to use for uploading files. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] connection_name = os.environ.get("CONNECTION_NAME") dataset_name = os.environ.get("DATASET_NAME", "dataset-test") dataset_version = os.environ.get("DATASET_VERSION", "1.0") diff --git a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py index cee409590d7a..203015acdad7 100644 --- a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py +++ b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. + 2) FOUNDRY_MODEL_NAME - Required. The name of the deployment to retrieve. 3) MODEL_PUBLISHER - Optional. The publisher of the model to filter by. 4) MODEL_NAME - Optional. The name of the model to filter by. """ @@ -31,8 +31,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] model_publisher = os.environ.get("MODEL_PUBLISHER", "Microsoft") model_name = os.environ.get("MODEL_NAME", "Phi-4") diff --git a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py index ae2a1151ba17..bc31782e60f1 100644 --- a/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py +++ b/sdk/ai/azure-ai-projects/samples/deployments/sample_deployments_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the deployment to retrieve. + 2) FOUNDRY_MODEL_NAME - Required. The name of the deployment to retrieve. 3) MODEL_PUBLISHER - Optional. The publisher of the model to filter by. 4) MODEL_NAME - Optional. The name of the model to filter by. """ @@ -35,8 +35,8 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] - model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] model_publisher = os.environ.get("MODEL_PUBLISHER", "Microsoft") model_name = os.environ.get("MODEL_NAME", "Phi-4") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/README.md b/sdk/ai/azure-ai-projects/samples/evaluations/README.md index 628468aa20e4..b87c5f25d0b5 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/README.md +++ b/sdk/ai/azure-ai-projects/samples/evaluations/README.md @@ -11,8 +11,8 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv ``` Set these environment variables: -- `AZURE_AI_PROJECT_ENDPOINT` - Your Azure AI Project endpoint (e.g., `https://.services.ai.azure.com/api/projects/`) -- `AZURE_AI_MODEL_DEPLOYMENT_NAME` - The model deployment name (e.g., `gpt-4o-mini`) +- `FOUNDRY_PROJECT_ENDPOINT` - Your Azure AI Project endpoint (e.g., `https://.services.ai.azure.com/api/projects/`) +- `FOUNDRY_MODEL_NAME` - The model deployment name (e.g., `gpt-4o-mini`) ## Sample Index @@ -94,8 +94,8 @@ Located in the [agentic_evaluators](https://github.com/Azure/azure-sdk-for-pytho ```bash # Set environment variables -export AZURE_AI_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" -export AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini" # Replace with your model +export FOUNDRY_PROJECT_ENDPOINT="https://.services.ai.azure.com/api/projects/" +export FOUNDRY_MODEL_NAME="gpt-4o-mini" # Replace with your model # Run a sample python sample_evaluations_builtin_with_inline_data.py diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py index 13bbaf3726bc..e3c2f9291a09 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py index dc0a7199df78..5ab4e46690ac 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py index 1650b0d1cc5d..68ef5ef7bbc3 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py @@ -28,7 +28,7 @@ def run_evaluator( data_mapping: dict[str, str], ) -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py index df15b21b6699..b5be2881ae38 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -36,7 +36,7 @@ def _get_evaluator_initialization_parameters(evaluator_name: str) -> dict[str, s if evaluator_name == "task_navigation_efficiency": return {"matching_mode": "exact_match"} # Can be "exact_match", "in_order_match", or "any_order_match" else: - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini return {"deployment_name": model_deployment_name} diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py index 654adfb1faec..8aa48ca726a6 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py index e434a78db732..4601d4587925 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py index 1d3cc28dc6cb..553770301b91 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py index cd6ce53055ea..c37eafae3d41 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py index b2f5381c54e4..30066c7ff66d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py index 92d39f2ddddc..73dce3ae4070 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py index 6c9bc015d529..47dd01c5eeb0 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ @@ -42,7 +42,7 @@ def main() -> None: endpoint = os.environ.get( - "AZURE_AI_PROJECT_ENDPOINT", "" + "FOUNDRY_PROJECT_ENDPOINT", "" ) # Sample : https://.services.ai.azure.com/api/projects/ with ( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py index c2f8980e503e..d3aea2348416 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py index 5404c8bb1183..12e1746db771 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -44,9 +44,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py index 5809882fb1ee..b08119e4f88c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py index 3437f8fd674f..72ea0e1f260c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py index e7646aeaabe8..38f49bc9c582 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ from dotenv import load_dotenv @@ -43,9 +43,9 @@ def main() -> None: endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ - model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py index 7cafd28186f2..bc54ebd5c70d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py @@ -19,10 +19,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_AGENT_NAME - The name of the AI agent to use for evaluation. + 3) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,8 +39,8 @@ from openai.types.evals.run_retrieve_response import RunRetrieveResponse load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini # [START agent_evaluation_basic] with ( @@ -49,7 +49,7 @@ project_client.get_openai_client() as openai_client, ): agent = project_client.agents.create_version( - agent_name=os.environ["AZURE_AI_AGENT_NAME"], + agent_name=os.environ["FOUNDRY_AGENT_NAME"], definition=PromptAgentDefinition( model=model_deployment_name, instructions="You are a helpful assistant that answers general questions", diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py index 9c81014daa52..73b32e3c8f26 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py @@ -19,10 +19,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_AGENT_NAME - The name of the AI agent to use for evaluation. + 3) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -48,9 +48,9 @@ ): agent = project_client.agents.create_version( - agent_name=os.environ["AZURE_AI_AGENT_NAME"], + agent_name=os.environ["FOUNDRY_AGENT_NAME"], definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py index 37d7244c3808..45de7b5d2e6f 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -40,8 +40,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] # Define a function tool for the model to use func_tool = FunctionTool( diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py index 7c38cb9a2d5d..8c8ac624e790 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py @@ -28,10 +28,10 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_AGENT_NAME - The name of the AI agent to use for evaluation. - 3) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_AGENT_NAME - The name of the AI agent to use for evaluation. + 3) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -50,7 +50,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -61,9 +61,9 @@ # Create agent agent = project_client.agents.create_version( - agent_name=os.environ["AZURE_AI_AGENT_NAME"], + agent_name=os.environ["FOUNDRY_AGENT_NAME"], definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py index 255ea0c2e660..c1d788d294cb 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ @@ -41,7 +41,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py index c3227497204d..e00037924f8c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Optional. The name of the model deployment to use for evaluation. """ @@ -42,8 +42,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py index b3dfb581b3ce..e17293f21492 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Optional. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Optional. The name of the model deployment to use for evaluation. For Custom Prompt Based Evaluators: @@ -75,8 +75,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py index 77093c5d704b..256bacef18b4 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -51,11 +51,11 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME") if not model_deployment_name: - raise ValueError("AZURE_AI_MODEL_DEPLOYMENT_NAME environment variable is not set") + raise ValueError("FOUNDRY_MODEL_NAME environment variable is not set") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py index c0fe4424bc85..1cf98af0efce 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py @@ -21,9 +21,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -47,7 +47,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -64,7 +64,7 @@ TestingCriterionLabelModel( type="label_model", name="sentiment_analysis", - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input=[ { "role": "developer", diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py index 8ad0899d2a01..c76c08bf6191 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -39,8 +39,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py index c113c8b67e06..29559c76572a 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. 4) DATASET_VERSION - Optional. The version of the Dataset to create and use in this sample. 5) DATA_FOLDER - Optional. The folder path where the data files for upload are located. @@ -44,8 +44,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") dataset_name = os.environ.get("DATASET_NAME", "") dataset_version = os.environ.get("DATASET_VERSION", "1") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py index fa4ec52105a3..f18080047b25 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -41,9 +41,9 @@ endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini # Construct the paths to the data folder and data file used in this sample script_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py index 9d968bbbf296..8c8100efbca3 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py @@ -18,9 +18,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -42,10 +42,10 @@ client = OpenAI( api_key=get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default"), - base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") # Sample : gpt-4o-mini +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini data_source_config = DataSourceConfigCustom( { diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py index 33aff799eb4f..57f839a022ee 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py @@ -19,12 +19,12 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-monitor-query Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) APPINSIGHTS_RESOURCE_ID - Required. The Azure Application Insights resource ID that stores agent traces. It has the form: /subscriptions//resourceGroups//providers/Microsoft.Insights/components/. 3) AGENT_ID - Required. The agent identifier emitted by the Azure tracing integration, used to filter traces. - 4) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The Azure OpenAI deployment name to use with the built-in evaluators. + 4) FOUNDRY_MODEL_NAME - Required. The Azure OpenAI deployment name to use with the built-in evaluators. 5) TRACE_LOOKBACK_HOURS - Optional. Number of hours to look back when querying traces and in the evaluation run. Defaults to 1. """ @@ -44,12 +44,12 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] appinsights_resource_id = os.environ[ "APPINSIGHTS_RESOURCE_ID" ] # Sample : /subscriptions//resourceGroups//providers/Microsoft.Insights/components/ agent_id = os.environ["AGENT_ID"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] +model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] trace_query_hours = int(os.environ.get("TRACE_LOOKBACK_HOURS", "1")) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py index ecb0f7fc86c7..531924ed51bc 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -39,8 +39,8 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py index 0f9377ef9fbf..8273afaa747e 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv pillow Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. The name of the model deployment to use for evaluation. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ import os @@ -47,8 +47,8 @@ file_path = os.path.abspath(__file__) folder_path = os.path.dirname(file_path) -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] -model_deployment_name = os.environ.get("AZURE_AI_MODEL_DEPLOYMENT_NAME", "") +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") def image_to_data_uri(image_path: str) -> str: diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py index 8b0170031dd3..53066a0fce99 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -39,7 +39,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -68,7 +68,7 @@ ) print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") - model = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] + model = os.environ["FOUNDRY_MODEL_NAME"] data_source = { "type": "azure_ai_target_completions", "source": { diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py index 6516c637796e..b9ff12eba463 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py @@ -17,9 +17,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. - 2) AZURE_AI_AGENT_NAME - Required. The name of the Agent to perform red teaming evaluation on. + 2) FOUNDRY_AGENT_NAME - Required. The name of the Agent to perform red teaming evaluation on. """ import os @@ -45,8 +45,8 @@ def main() -> None: load_dotenv() # - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") - agent_name = os.environ.get("AZURE_AI_AGENT_NAME", "") + endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT", "") + agent_name = os.environ.get("FOUNDRY_AGENT_NAME", "") with ( DefaultAzureCredential() as credential, @@ -56,7 +56,7 @@ def main() -> None: agent_version = project_client.agents.create_version( agent_name=agent_name, definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py index 29397e006414..742c6e100070 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py @@ -17,14 +17,14 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-mgmt-authorization azure-mgmt-resource Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. 2) AZURE_SUBSCRIPTION_ID - Required for RBAC assignment. The Azure subscription ID where the project is located. 3) AZURE_RESOURCE_GROUP_NAME - Required for RBAC assignment. The resource group name where the project is located. 4) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. 5) DATASET_VERSION - Optional. The version of the Dataset to create and use in this sample. 6) DATA_FOLDER - Optional. The folder path where the data files for upload are located. - 7) AZURE_AI_AGENT_NAME - Required. The name of the Agent to perform red teaming evaluation on. + 7) FOUNDRY_AGENT_NAME - Required. The name of the Agent to perform red teaming evaluation on. """ from datetime import datetime @@ -75,13 +75,13 @@ def assign_rbac(): """ load_dotenv() - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") + endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT", "") subscription_id = os.environ.get("AZURE_SUBSCRIPTION_ID", "") resource_group_name = os.environ.get("AZURE_RESOURCE_GROUP_NAME", "") if not endpoint or not subscription_id or not resource_group_name: print( - "Error: AZURE_AI_PROJECT_ENDPOINT, AZURE_SUBSCRIPTION_ID, and AZURE_RESOURCE_GROUP_NAME environment variables are required" + "Error: FOUNDRY_PROJECT_ENDPOINT, AZURE_SUBSCRIPTION_ID, and AZURE_RESOURCE_GROUP_NAME environment variables are required" ) return @@ -214,7 +214,7 @@ def assign_rbac(): def schedule_dataset_evaluation() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] dataset_name = os.environ.get("DATASET_NAME", "") dataset_version = os.environ.get("DATASET_VERSION", "1") # Construct the paths to the data folder and data file used in this sample @@ -327,8 +327,8 @@ def schedule_dataset_evaluation() -> None: def schedule_redteam_evaluation() -> None: load_dotenv() # - endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT", "") - agent_name = os.environ.get("AZURE_AI_AGENT_NAME", "") + endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT", "") + agent_name = os.environ.get("FOUNDRY_AGENT_NAME", "") # Construct the paths to the data folder and data file used in this sample script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -343,7 +343,7 @@ def schedule_redteam_evaluation() -> None: agent_version = project_client.agents.create_version( agent_name=agent_name, definition=PromptAgentDefinition( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions="You are a helpful assistant that answers general questions", ), ) diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files.py b/sdk/ai/azure-ai-projects/samples/files/sample_files.py index 57f8e3fb3187..51dac448a43e 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files.py @@ -16,7 +16,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) FILE_PATH - Optional. Path to the file to upload. Defaults to the `data` folder. """ @@ -29,7 +29,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] script_dir = Path(__file__).parent file_path = os.environ.get("FILE_PATH", os.path.join(script_dir, "data", "test_file.jsonl")) diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py index d6bb1491a678..953fa2021c1b 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py @@ -16,7 +16,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) FILE_PATH - Optional. Path to the file to upload. Defaults to the `data` folder. """ @@ -30,7 +30,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] script_dir = Path(__file__).parent file_path = os.environ.get("FILE_PATH", os.path.join(script_dir, "data", "test_file.jsonl")) diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py index b8fe46419490..2a31c816741e 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4o` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "dpo_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "dpo_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py index 4e7b5dc91ec0..a21e9ca10387 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4o` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4o-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "dpo_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "dpo_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py index c4a44e2d727f..f3eebc756dfc 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "Ministral-3B") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py index 2fcd5ee8e93c..676a4a030d11 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py @@ -18,7 +18,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `Ministral-3B` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "Ministral-3B") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py index 6b1bed171863..9a222cffa076 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `o4-mini` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "o4-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "rft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "rft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py index 701bf384731f..3d759737fbab 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `o4-mini` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "o4-mini") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "rft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "rft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py index 626af3c4bedd..a871254e90dd 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py @@ -21,7 +21,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv azure-mgmt-cognitiveservices Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -43,7 +43,7 @@ load_dotenv() # For fine-tuning -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4.1") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py index cd967034fd92..94e5d2c94603 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py @@ -21,7 +21,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp azure-mgmt-cognitiveservices Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry portal. 2) MODEL_NAME - Optional. The base model name to use for fine-tuning. Default to the `gpt-4.1` model. 3) TRAINING_FILE_PATH - Optional. Path to the training data file. Default to the `data` folder. @@ -43,7 +43,7 @@ load_dotenv() # For fine-tuning -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_name = os.environ.get("MODEL_NAME", "gpt-4.1") training_file_path = resolve_data_file_path(__file__, "TRAINING_FILE_PATH", "sft_training_set.jsonl") validation_file_path = resolve_data_file_path(__file__, "VALIDATION_FILE_PATH", "sft_validation_set.jsonl") diff --git a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py index fd865b8d8ca1..04ac684cb8d3 100644 --- a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py +++ b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) INDEX_NAME - Optional. The name of the Index to create and use in this sample. 3) INDEX_VERSION - Optional. The version of the Index to create and use in this sample. @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] index_name = os.environ.get("INDEX_NAME", "index-test") index_version = os.environ.get("INDEX_VERSION", "1.0") ai_search_connection_name = os.environ.get("AI_SEARCH_CONNECTION_NAME", "my-ai-search-connection-name") diff --git a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py index 9ace80a10df8..d83267929313 100644 --- a/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py +++ b/sdk/ai/azure-ai-projects/samples/indexes/sample_indexes_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. 2) INDEX_NAME - Optional. The name of the Index to create and use in this sample. 3) INDEX_VERSION - Optional. The version of the Index to create and use in this sample. @@ -37,7 +37,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] index_name = os.environ.get("INDEX_NAME", "index-test") index_version = os.environ.get("INDEX_VERSION", "1.0") ai_search_connection_name = os.environ.get("AI_SEARCH_CONNECTION_NAME", "my-ai-search-connection-name") diff --git a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py index d09bb48a72ad..f84e6358d7b0 100644 --- a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py @@ -8,7 +8,7 @@ DESCRIPTION: This sample demonstrates how to directly interact with MCP (Model Context Protocol) tools using the low-level MCP client library to connect to the Foundry Project's MCP tools API: - {AZURE_AI_PROJECT_ENDPOINT}/mcp_tools?api-version=2025-05-15-preview + {FOUNDRY_PROJECT_ENDPOINT}/mcp_tools?api-version=2025-05-15-preview For agent-based MCP tool usage, see samples in samples/agents/tools/sample_agent_mcp.py and related files in that directory. @@ -29,7 +29,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv mcp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) IMAGE_GEN_DEPLOYMENT_NAME - The deployment name of the image generation model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -64,7 +64,7 @@ # Enable httpx logging to see HTTP requests at the same level logging.getLogger("httpx").setLevel(getattr(logging, log_level, logging.CRITICAL)) -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main(): diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py index 6af7c9c90f3c..dc0e03d66528 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py @@ -24,7 +24,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -46,7 +46,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py index f8b9643f7547..4192fe11ee57 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py @@ -24,7 +24,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -50,7 +50,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py index 0d3cf82f4fbe..bf87aee7334b 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic.py @@ -22,7 +22,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -43,7 +43,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py index 6999a6c154ca..ccd5e77a9b0e 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_basic_async.py @@ -23,7 +23,7 @@ Once you have deployed models, set the deployment name in the variables below. Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -48,7 +48,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py index 94d1abdab835..38341435e49f 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud.py @@ -20,7 +20,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -37,7 +37,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential(exclude_interactive_browser_credential=False) as credential, diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py index 730dd3a53534..9dad4f62f2eb 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_crud_async.py @@ -20,7 +20,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. 2) MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME - The deployment name of the chat model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. @@ -41,7 +41,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py index 918ae00569e5..eebf4356fee0 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. + 2) FOUNDRY_MODEL_NAME - Required. Your model deployment name. 3) MODEL_ENDPOINT - Required. The Azure AI Model endpoint, as found in the overview page of your Microsoft Foundry project. Example: https://.services.ai.azure.com 4) MODEL_API_KEY - Required. The API key for your Azure AI Model. @@ -38,11 +38,11 @@ load_dotenv() endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ model_endpoint = os.environ["MODEL_ENDPOINT"] # Sample : https://.services.ai.azure.com model_api_key = os.environ["MODEL_API_KEY"] -model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini +model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] # Sample : gpt-4o-mini with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py index 261b0f9aaedc..016cc1d532f2 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py @@ -16,9 +16,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - Required. Your model deployment name. + 2) FOUNDRY_MODEL_NAME - Required. Your model deployment name. 3) MODEL_ENDPOINT - Required. The Azure AI Model endpoint, as found in the overview page of your Microsoft Foundry project. Example: https://.services.ai.azure.com 4) MODEL_API_KEY - Required. The API key for your Azure AI Model. @@ -43,11 +43,11 @@ async def sample_red_team_async() -> None: """Demonstrates how to perform Red Team operations using the AIProjectClient.""" endpoint = os.environ[ - "AZURE_AI_PROJECT_ENDPOINT" + "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ model_endpoint = os.environ["MODEL_ENDPOINT"] # Sample : https://.services.ai.azure.com model_api_key = os.environ["MODEL_API_KEY"] - model_deployment_name = os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"] # Sample : gpt-4o-mini + model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] # Sample : gpt-4o-mini async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py index 436237b03c46..5840f19b37d2 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -31,7 +31,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -40,13 +40,13 @@ # [START responses] with project_client.get_openai_client() as openai_client: response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py index b68ab13eddd7..24fddd499dbd 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_async.py @@ -18,9 +18,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async def main() -> None: @@ -43,13 +43,13 @@ async def main() -> None: project_client.get_openai_client() as openai_client, ): response = await openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="What is the size of France in square miles?", ) print(f"Response output: {response.output_text}") response = await openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="And what is the capital city?", previous_response_id=response.id, ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py index 9c5d8b656bd7..edc4a40f4d81 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient.py @@ -19,9 +19,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,11 +34,11 @@ openai = OpenAI( api_key=get_bearer_token_provider(DefaultAzureCredential(), "https://ai.azure.com/.default"), - base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) response = openai.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="How many feet are in a mile?", ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py index 8a2934ff7418..e437bcb6e1db 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_basic_without_aiprojectclient_async.py @@ -19,9 +19,9 @@ pip install openai azure-identity python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,13 +42,13 @@ async def main() -> None: openai = AsyncOpenAI( api_key=get_bearer_token_provider(credential, "https://ai.azure.com/.default"), - base_url=os.environ["AZURE_AI_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", + base_url=os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/") + "/openai/v1", ) async with openai: response = await openai.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input="How many feet are in a mile?", ) diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py index 542d956b1cf0..b833b23fe052 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_image_input.py @@ -19,9 +19,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] def image_to_base64(image_path: str) -> str: @@ -71,6 +71,6 @@ def image_to_base64(image_path: str) -> str: ], } ], - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], ) print(f"Response output: {response.output_text}") diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py index 86f3b2d1fa47..065046543a61 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -34,7 +34,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -43,7 +43,7 @@ ): with openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input=[ {"role": "user", "content": "Tell me about the capital city of France"}, ], diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py index 110bb0b1c4be..87db2ada4d88 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -33,7 +33,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -42,7 +42,7 @@ ): with openai_client.responses.stream( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], input=[ {"role": "user", "content": "Tell me about the capital city of France"}, ], diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py index d1c75654083a..1119d969df9c 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py @@ -20,9 +20,9 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview page of your Microsoft Foundry portal. - 2) AZURE_AI_MODEL_DEPLOYMENT_NAME - The deployment name of the AI model, as found under the "Name" column in + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in the "Models + endpoints" tab in your Microsoft Foundry project. """ @@ -42,7 +42,7 @@ class CalendarEvent(BaseModel): participants: list[str] -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, @@ -50,7 +50,7 @@ class CalendarEvent(BaseModel): project_client.get_openai_client() as openai_client, ): response = openai_client.responses.create( - model=os.environ["AZURE_AI_MODEL_DEPLOYMENT_NAME"], + model=os.environ["FOUNDRY_MODEL_NAME"], instructions=""" Extracts calendar event information from the input messages, and return it in the desired structured output format. diff --git a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py index 61f11436620b..9820a418f773 100644 --- a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py +++ b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. """ @@ -28,7 +28,7 @@ load_dotenv() -endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py index 7554370401f4..92a76de01f8f 100644 --- a/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py +++ b/sdk/ai/azure-ai-projects/samples/telemetry/sample_telemetry_async.py @@ -17,7 +17,7 @@ pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp Set these environment variables with your own values: - 1) AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the overview page of your Microsoft Foundry project. """ @@ -32,7 +32,7 @@ async def main() -> None: - endpoint = os.environ["AZURE_AI_PROJECT_ENDPOINT"] + endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] async with ( DefaultAzureCredential() as credential, diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py index d7aede04fb65..bdd35586e4e0 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py @@ -273,7 +273,7 @@ def _test_agent_creation_with_tracing_content_recording_enabled_impl(self, use_e with self.create_client(operation_group="tracing", **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") print(f"Using model deployment: {model}") agent_definition = PromptAgentDefinition( @@ -387,7 +387,7 @@ def _test_agent_creation_with_tracing_content_recording_disabled_impl(self, use_ with self.create_client(operation_group="agents", **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") agent_definition = PromptAgentDefinition( model=model, instructions="You are a helpful AI assistant. Always be polite and provide accurate information.", @@ -609,7 +609,7 @@ def _test_agent_with_structured_output_with_instructions_impl( operation_group = "tracing" if content_recording_enabled else "agents" with self.create_client(operation_group=operation_group, **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") test_schema = { "type": "object", @@ -797,7 +797,7 @@ def _test_agent_with_structured_output_without_instructions_impl( operation_group = "tracing" if content_recording_enabled else "agents" with self.create_client(operation_group=operation_group, **kwargs) as project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") test_schema = { "type": "object", diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py index 96184005a350..e1bbef6d0511 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py @@ -75,7 +75,7 @@ async def _test_create_agent_with_tracing_content_recording_enabled_impl(self, u assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with project_client: agent_definition = PromptAgentDefinition( @@ -186,7 +186,7 @@ async def _test_agent_creation_with_tracing_content_recording_disabled_impl(self assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="agents", **kwargs) - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with project_client: agent_definition = PromptAgentDefinition( @@ -406,7 +406,7 @@ async def _test_agent_with_structured_output_with_instructions_impl( project_client = self.create_async_client(operation_group=operation_group, **kwargs) async with project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") test_schema = { "type": "object", @@ -591,7 +591,7 @@ async def _test_agent_with_structured_output_without_instructions_impl( project_client = self.create_async_client(operation_group=operation_group, **kwargs) async with project_client: - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") test_schema = { "type": "object", diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py index 7b8471717f2b..7556c4933eaa 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py @@ -78,7 +78,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: openai_client = project_client.get_openai_client() # Get the model deployment name from test parameters - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_deployment_name = kwargs.get("foundry_model_name") return openai_client, model_deployment_name @@ -234,7 +234,7 @@ def _test_sync_non_streaming_with_content_recording_impl(self, use_events, **kwa with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -356,7 +356,7 @@ def _test_sync_non_streaming_without_content_recording_impl(self, use_events, ** with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -479,7 +479,7 @@ def _test_sync_streaming_with_content_recording_impl(self, use_events, **kwargs) with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -656,7 +656,7 @@ def test_sync_conversations_create(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -701,7 +701,7 @@ def test_sync_list_conversation_items_with_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -778,7 +778,7 @@ def test_sync_list_conversation_items_without_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -887,7 +887,7 @@ def _test_sync_non_streaming_without_conversation_impl(self, use_events, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") with project_client: # Get the OpenAI client from the project client @@ -994,7 +994,7 @@ def _test_sync_function_tool_with_content_recording_non_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -1253,7 +1253,7 @@ def _test_sync_function_tool_with_content_recording_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -1577,7 +1577,7 @@ def _test_sync_function_tool_without_content_recording_non_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -1813,7 +1813,7 @@ def _test_sync_function_tool_without_content_recording_streaming_impl( with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -2131,7 +2131,7 @@ def test_sync_function_tool_list_conversation_items_with_content_recording(self, with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -2281,7 +2281,7 @@ def test_sync_function_tool_list_conversation_items_without_content_recording(se with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -2429,7 +2429,7 @@ def test_sync_multiple_text_inputs_with_content_recording_non_streaming(self, ** with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -2526,7 +2526,7 @@ def test_sync_multiple_text_inputs_with_content_recording_streaming(self, **kwar with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -2631,7 +2631,7 @@ def test_sync_multiple_text_inputs_without_content_recording_non_streaming(self, with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -2723,7 +2723,7 @@ def test_sync_multiple_text_inputs_without_content_recording_streaming(self, **k with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -2824,7 +2824,7 @@ def _test_image_only_content_off_binary_off_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -2929,7 +2929,7 @@ def _test_image_only_content_off_binary_on_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3033,7 +3033,7 @@ def _test_image_only_content_on_binary_off_non_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3137,7 +3137,7 @@ def _test_image_only_content_on_binary_on_non_streaming_impl(self, use_events, * with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3245,7 +3245,7 @@ def _test_text_and_image_content_off_binary_off_non_streaming_impl(self, use_eve with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3354,7 +3354,7 @@ def _test_text_and_image_content_off_binary_on_non_streaming_impl(self, use_even with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3463,7 +3463,7 @@ def _test_text_and_image_content_on_binary_off_non_streaming_impl(self, use_even with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3571,7 +3571,7 @@ def _test_text_and_image_content_on_binary_on_non_streaming_impl(self, use_event with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3683,7 +3683,7 @@ def _test_image_only_content_off_binary_off_streaming_impl(self, use_events, **k with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3796,7 +3796,7 @@ def _test_image_only_content_off_binary_on_streaming_impl(self, use_events, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -3908,7 +3908,7 @@ def _test_image_only_content_on_binary_off_streaming_impl(self, use_events, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4020,7 +4020,7 @@ def _test_image_only_content_on_binary_on_streaming_impl(self, use_events, **kwa with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4136,7 +4136,7 @@ def _test_text_and_image_content_off_binary_off_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4253,7 +4253,7 @@ def _test_text_and_image_content_off_binary_on_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4370,7 +4370,7 @@ def _test_text_and_image_content_on_binary_off_streaming_impl(self, use_events, with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4486,7 +4486,7 @@ def _test_text_and_image_content_on_binary_on_streaming_impl(self, use_events, * with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4601,7 +4601,7 @@ def test_responses_stream_method_with_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4654,7 +4654,7 @@ def test_responses_stream_method_without_content_recording(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = client.conversations.create() @@ -4709,7 +4709,7 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool function_tool = FunctionTool( @@ -4823,7 +4823,7 @@ def test_responses_stream_method_with_tools_without_content_recording(self, **kw with self.create_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool function_tool = FunctionTool( @@ -4960,7 +4960,7 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): assert True == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") openai_client = project_client.get_openai_client() # Create Teacher Agent @@ -5159,7 +5159,7 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): assert False == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") openai_client = project_client.get_openai_client() workflow_yaml = """ @@ -5276,7 +5276,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): assert True == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") openai_client = project_client.get_openai_client() # Create Teacher Agent @@ -5478,7 +5478,7 @@ def test_workflow_agent_streaming_without_content_recording(self, **kwargs): assert False == AIProjectInstrumentor().is_content_recording_enabled() with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") openai_client = project_client.get_openai_client() workflow_yaml = """ diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py index 8f2b18126533..227b353fa800 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py @@ -59,7 +59,7 @@ async def _test_async_non_streaming_with_content_recording_impl(self, use_events assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: # Get the OpenAI client from the project client @@ -165,7 +165,7 @@ async def _test_async_streaming_with_content_recording_impl(self, use_events, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: # Get the OpenAI client from the project client @@ -277,7 +277,7 @@ async def test_async_conversations_create(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: # Get the OpenAI client from the project client @@ -325,7 +325,7 @@ async def test_async_list_conversation_items_with_content_recording(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: # Get the OpenAI client from the project client @@ -417,7 +417,7 @@ async def _test_async_function_tool_with_content_recording_streaming_impl( async with project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -678,7 +678,7 @@ async def _test_async_function_tool_without_content_recording_streaming_impl( async with project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool func_tool = FunctionTool( @@ -924,7 +924,7 @@ async def test_async_multiple_text_inputs_with_content_recording_non_streaming(s async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = await client.conversations.create() @@ -1021,7 +1021,7 @@ async def test_async_multiple_text_inputs_with_content_recording_streaming(self, async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = await client.conversations.create() @@ -1126,7 +1126,7 @@ async def test_async_multiple_text_inputs_without_content_recording_non_streamin async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = await client.conversations.create() @@ -1225,7 +1225,7 @@ async def test_async_image_only_content_off_binary_off_non_streaming(self, **kwa assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1297,7 +1297,7 @@ async def test_async_image_only_content_off_binary_on_non_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1368,7 +1368,7 @@ async def test_async_image_only_content_on_binary_off_non_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1439,7 +1439,7 @@ async def test_async_image_only_content_on_binary_on_non_streaming(self, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1514,7 +1514,7 @@ async def test_async_text_and_image_content_off_binary_off_non_streaming(self, * assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1590,7 +1590,7 @@ async def test_async_text_and_image_content_off_binary_on_non_streaming(self, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1665,7 +1665,7 @@ async def test_async_text_and_image_content_on_binary_off_non_streaming(self, ** assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1740,7 +1740,7 @@ async def test_async_text_and_image_content_on_binary_on_non_streaming(self, **k assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1819,7 +1819,7 @@ async def test_async_image_only_content_off_binary_off_streaming(self, **kwargs) assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1899,7 +1899,7 @@ async def test_async_image_only_content_off_binary_on_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -1978,7 +1978,7 @@ async def test_async_image_only_content_on_binary_off_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -2057,7 +2057,7 @@ async def test_async_image_only_content_on_binary_on_streaming(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -2140,7 +2140,7 @@ async def test_async_text_and_image_content_off_binary_off_streaming(self, **kwa assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -2223,7 +2223,7 @@ async def test_async_text_and_image_content_off_binary_on_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -2306,7 +2306,7 @@ async def test_async_text_and_image_content_on_binary_off_streaming(self, **kwar assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -2389,7 +2389,7 @@ async def test_async_text_and_image_content_on_binary_on_streaming(self, **kwarg assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -2474,7 +2474,7 @@ async def test_async_multiple_text_inputs_without_content_recording_streaming(se async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = await client.conversations.create() @@ -2582,7 +2582,7 @@ async def test_async_responses_stream_method_with_content_recording(self, **kwar async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = await client.conversations.create() @@ -2662,7 +2662,7 @@ async def test_async_responses_stream_method_without_content_recording(self, **k async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") conversation = await client.conversations.create() @@ -2750,7 +2750,7 @@ async def _test_async_responses_stream_method_with_tools_with_content_recording_ async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool function_tool = FunctionTool( @@ -2983,7 +2983,7 @@ async def _test_async_responses_stream_method_with_tools_without_content_recordi async with self.create_async_client(operation_group="tracing", **kwargs) as project_client: client = project_client.get_openai_client() - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") # Define a function tool function_tool = FunctionTool( @@ -3214,7 +3214,7 @@ async def test_async_workflow_agent_non_streaming_with_content_recording(self, * assert True == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: # Create a simple workflow agent @@ -3331,7 +3331,7 @@ async def test_async_workflow_agent_non_streaming_without_content_recording(self assert False == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: workflow_yaml = """ @@ -3454,7 +3454,7 @@ async def test_async_workflow_agent_streaming_with_content_recording(self, **kwa assert True == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: workflow_yaml = """ @@ -3575,7 +3575,7 @@ async def test_async_workflow_agent_streaming_without_content_recording(self, ** assert False == AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: workflow_yaml = """ @@ -3708,7 +3708,7 @@ async def _test_async_prompt_agent_with_responses_non_streaming_impl( assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() @@ -3846,7 +3846,7 @@ async def _test_async_prompt_agent_with_responses_streaming_impl( assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") async with project_client: client = project_client.get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py index f68d4fdae952..592cf8b36b54 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py @@ -59,7 +59,7 @@ def test_sync_browser_automation_non_streaming_with_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -194,7 +194,7 @@ def test_sync_browser_automation_non_streaming_without_content_recording(self, * assert not AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -320,7 +320,7 @@ def test_sync_browser_automation_streaming_with_content_recording(self, **kwargs self.setup_telemetry() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -448,7 +448,7 @@ def test_sync_browser_automation_streaming_without_content_recording(self, **kwa self.setup_telemetry() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py index 46918894c2fb..55e3c949804f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py @@ -62,7 +62,7 @@ async def test_async_browser_automation_non_streaming_with_content_recording(sel assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -193,7 +193,7 @@ async def test_async_browser_automation_non_streaming_without_content_recording( assert not AIProjectInstrumentor().is_content_recording_enabled() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -315,7 +315,7 @@ async def test_async_browser_automation_streaming_with_content_recording(self, * self.setup_telemetry() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: @@ -440,7 +440,7 @@ async def test_async_browser_automation_streaming_without_content_recording(self self.setup_telemetry() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") browser_automation_connection_id = kwargs.get("browser_automation_project_connection_id") assert deployment_name is not None if browser_automation_connection_id is None: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py index 331d64b9aaa8..c282164e3fcf 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py @@ -66,7 +66,7 @@ def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwar assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -254,7 +254,7 @@ def test_sync_code_interpreter_non_streaming_without_content_recording(self, **k assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -445,7 +445,7 @@ def test_sync_code_interpreter_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -636,7 +636,7 @@ def test_sync_code_interpreter_streaming_without_content_recording(self, **kwarg assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py index 7e5512b6fbbe..ac91f4b83565 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py @@ -67,7 +67,7 @@ async def test_async_code_interpreter_non_streaming_with_content_recording(self, assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -254,7 +254,7 @@ async def test_async_code_interpreter_non_streaming_without_content_recording(se assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -445,7 +445,7 @@ async def test_async_code_interpreter_streaming_with_content_recording(self, **k assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -636,7 +636,7 @@ async def test_async_code_interpreter_streaming_without_content_recording(self, assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py index ca4f301212f3..17b4ba9285f3 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py @@ -53,7 +53,7 @@ def test_sync_file_search_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -262,7 +262,7 @@ def test_sync_file_search_non_streaming_without_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -469,7 +469,7 @@ def test_sync_file_search_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -674,7 +674,7 @@ def test_sync_file_search_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py index fd6c36261449..704d6bbb7117 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py @@ -54,7 +54,7 @@ async def test_async_file_search_non_streaming_with_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -263,7 +263,7 @@ async def test_async_file_search_non_streaming_without_content_recording(self, * assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -470,7 +470,7 @@ async def test_async_file_search_streaming_with_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -675,7 +675,7 @@ async def test_async_file_search_streaming_without_content_recording(self, **kwa assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py index 4c5c453a3e23..0ac08ad35e2a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py @@ -59,7 +59,7 @@ def _test_sync_mcp_non_streaming_with_content_recording_impl(self, use_events, * assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -389,7 +389,7 @@ def _test_sync_mcp_non_streaming_without_content_recording_impl(self, use_events assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -706,7 +706,7 @@ def _test_sync_mcp_streaming_with_content_recording_impl(self, use_events, **kwa assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -982,7 +982,7 @@ def _test_sync_mcp_streaming_without_content_recording_impl(self, use_events, ** assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py index d9e82e2951e8..e6401924773a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py @@ -60,7 +60,7 @@ async def _test_async_mcp_non_streaming_with_content_recording_impl(self, use_ev assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -389,7 +389,7 @@ async def _test_async_mcp_non_streaming_without_content_recording_impl(self, use assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -708,7 +708,7 @@ async def _test_async_mcp_streaming_with_content_recording_impl(self, use_events assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -987,7 +987,7 @@ async def _test_async_mcp_streaming_without_content_recording_impl(self, use_eve assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py index a198327679c3..57f17169897b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py @@ -41,7 +41,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: openai_client = project_client.get_openai_client() # Get the model deployment name from test parameters - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_deployment_name = kwargs.get("foundry_model_name") return openai_client, model_deployment_name diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py index bec6cfa9f2be..7e390d3e3a3a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py @@ -206,7 +206,7 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -371,7 +371,7 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -538,7 +538,7 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: @@ -706,7 +706,7 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py index e366e1ec3ef5..75808d2f8a2d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py @@ -205,7 +205,7 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -366,7 +366,7 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -531,7 +531,7 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: @@ -697,7 +697,7 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs assert AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("azure_ai_model_deployment_name") + deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None async with project_client: diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py index 1cc36a6b0455..44c8c315389e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py @@ -48,7 +48,7 @@ def test_agent_responses_crud(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -158,7 +158,7 @@ def test_agent_responses_crud(self, **kwargs): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_agent_responses_with_structured_output(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py index b710851c366f..27151090e10a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py @@ -23,7 +23,7 @@ class TestAgentResponsesCrudAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_responses_crud_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) @@ -129,7 +129,7 @@ async def test_agent_responses_crud_async(self, **kwargs): @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_responses_with_structured_output_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py index 10414b7a59d1..2e6451effb59 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py @@ -39,7 +39,7 @@ def test_agents_crud(self, **kwargs): GET /agents/{agent_name}/versions/{agent_version} project_client.agents.get_version() """ print("\n") - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") project_client = self.create_client(operation_group="agents", **kwargs) first_agent_name = "MyAgent1" second_agent_name = "MyAgent2" diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py index e9776b7e6257..6eb878e729b8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py @@ -24,7 +24,7 @@ async def test_agents_crud_async(self, **kwargs): It then gets, lists, and deletes them, validating at each step. It uses different ways of creating agents: strongly typed, dictionary, and IO[bytes]. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") project_client = self.create_async_client(operation_group="agents", **kwargs) first_agent_name = "MyAgent1" second_agent_name = "MyAgent2" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py index 3953bf1c76d2..4ef0e6b845f6 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py @@ -40,7 +40,7 @@ def test_calculate_and_save(self, **kwargs): 2. Function Tool: Saves the computed result """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -100,7 +100,7 @@ def test_generate_data_and_report(self, **kwargs): 2. Function Tool: Creates a report with the computed statistics """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py index 16f2c2c1ba41..f4996551357b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py @@ -39,7 +39,7 @@ def test_find_and_analyze_data(self, **kwargs): 2. Code Interpreter: Agent calculates the average of those numbers """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -121,7 +121,7 @@ def test_analyze_code_file(self, **kwargs): 2. Code Interpreter: Agent executes the code and returns the computed result """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py index f67e95c020a8..9adb1329774a 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py @@ -32,7 +32,7 @@ def test_data_analysis_workflow(self, **kwargs): Test data analysis workflow: upload data, search, save results. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -163,7 +163,7 @@ def test_empty_vector_store_handling(self, **kwargs): Test how agent handles empty vector store (no files uploaded). """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -242,7 +242,7 @@ def test_python_code_file_search(self, **kwargs): 2. Function Tool: Agent saves the code review findings """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) @@ -372,7 +372,7 @@ def test_multi_turn_search_and_save_workflow(self, **kwargs): - Context retention across searches and function calls """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py index 61d572fa0a37..648a1ed65519 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py @@ -43,7 +43,7 @@ def test_complete_analysis_workflow(self, **kwargs): 3. Function Tool: Agent saves the computed results """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py index 1ae26a32a1a4..f4a20b8f21ad 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py @@ -39,7 +39,7 @@ def test_file_search_and_function_with_conversation(self, **kwargs): - Verifying conversation state preserves all tool interactions """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py index 02d341051ec8..e16a8e0e5722 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py @@ -81,7 +81,7 @@ def test_agent_ai_search_question_answering(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Get AI Search connection and index from environment ai_search_connection_id = kwargs.get("ai_search_project_connection_id") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py index 5bc67d9a2833..0ed41ca18661 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search_async.py @@ -148,7 +148,7 @@ async def test_agent_ai_search_question_answering_async_parallel(self, **kwargs) DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py index 78ec18081aa0..190183663823 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py @@ -45,7 +45,7 @@ def test_agent_bing_grounding(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Note: This test requires bing_project_connection_id environment variable # to be set with a valid Bing connection ID from the project @@ -145,7 +145,7 @@ def test_agent_bing_grounding_multiple_queries(self, **kwargs): Bing grounding and provide accurate responses with citations. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") bing_connection_id = kwargs.get("bing_project_connection_id") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py index 7b5d1ea27680..1d49bb0bbb6b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py @@ -41,7 +41,7 @@ def test_agent_code_interpreter_simple_math(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") agent_name = "code-interpreter-simple-agent" with ( @@ -125,7 +125,7 @@ def test_agent_code_interpreter_file_generation(self, **kwargs): DELETE /files/{file_id} openai_client.files.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py index d38d15b2bd0e..50a4b778cb07 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py @@ -28,7 +28,7 @@ async def test_agent_code_interpreter_simple_math_async(self, **kwargs): without any file uploads or downloads - just pure code execution. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") agent_name = "code-interpreter-simple-agent-async" async with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py index e7408afe97fa..9c733529ac42 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py @@ -45,7 +45,7 @@ def test_agent_file_search(self, **kwargs): DELETE /vector_stores/{id} openai_client.vector_stores.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -203,7 +203,7 @@ def test_agent_file_search_multi_turn_conversation(self, **kwargs): while using File Search to answer follow-up questions. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py index e3d96f5a4733..604fbd5323fe 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py @@ -20,7 +20,7 @@ class TestAgentFileSearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_file_search_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, @@ -106,7 +106,7 @@ async def test_agent_file_search_multi_turn_conversation_async(self, **kwargs): while using File Search to answer follow-up questions. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py index e97814456771..72079587549e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py @@ -42,7 +42,7 @@ def test_agent_file_search_stream(self, **kwargs): DELETE /vector_stores/{id} openai_client.vector_stores.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py index fb4e627df2de..42e1348a3521 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py @@ -19,7 +19,7 @@ class TestAgentFileSearchStreamAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_file_search_stream_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py index 264bf97ebf73..05ebd8ddcad0 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py @@ -41,7 +41,7 @@ def test_agent_function_tool(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") agent_name = "function-tool-agent" with ( @@ -172,7 +172,7 @@ def test_agent_function_tool_multi_turn_with_multiple_calls(self, **kwargs): - Ability to use previous function results in subsequent queries """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -381,7 +381,7 @@ def test_agent_function_tool_context_dependent_followup(self, **kwargs): remembering parameters from the first query. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py index f4388b1ccfe9..2344c5a9d498 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py @@ -28,7 +28,7 @@ async def test_agent_function_tool_async(self, **kwargs): 3. Receive function results and incorporate them into responses """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") agent_name = "function-tool-agent-async" # Setup @@ -160,7 +160,7 @@ async def test_agent_function_tool_multi_turn_with_multiple_calls_async(self, ** - Ability to use previous function results in subsequent queries """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup project_client = self.create_async_client(operation_group="agents", **kwargs) @@ -370,7 +370,7 @@ async def test_agent_function_tool_context_dependent_followup_async(self, **kwar remembering parameters from the first query. """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") # Setup async with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py index c0c515839aaf..7dea648d735d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py @@ -41,7 +41,7 @@ def test_agent_image_generation(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") image_model = kwargs.get("image_generation_model_deployment_name") agent_name = "image-gen-agent" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py index a4775afb16b9..68b91adb44d4 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py @@ -21,7 +21,7 @@ class TestAgentImageGenerationAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_image_generation_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") image_model = kwargs.get("image_generation_model_deployment_name") agent_name = "image-gen-agent" diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py index 5723478f7569..109e1b93f775 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py @@ -48,7 +48,7 @@ def test_agent_mcp_basic(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -179,7 +179,7 @@ def test_agent_mcp_with_project_connection(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py index 36a951e79183..b9e1dd43c7e1 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py @@ -21,7 +21,7 @@ class TestAgentMCPAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_mcp_basic_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py index 3a1bc4e44d0d..56edc1879033 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py @@ -54,7 +54,7 @@ def test_agent_memory_search(self, **kwargs): DELETE /memory_stores/{memory_store_name} project_client.beta.memory_stores.delete() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") embedding_model = kwargs.get("memory_store_embedding_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py index dc6b69d22354..f830aac14fca 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py @@ -29,7 +29,7 @@ class TestAgentMemorySearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_memory_search_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") embedding_model = kwargs.get("memory_store_embedding_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py index de8b85f19723..0bb1aa3cdf33 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py @@ -51,7 +51,7 @@ def test_agent_openapi(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py index 1b3e87ef063a..dc363934a816 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py @@ -31,7 +31,7 @@ class TestAgentOpenApiAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_openapi_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py index 00fdf016e79d..1f4e5e78ef65 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py @@ -40,7 +40,7 @@ def test_function_tool_with_conversation(self, **kwargs): - Using conversation_id parameter """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -201,7 +201,7 @@ def test_file_search_with_conversation(self, **kwargs): - Conversation context retention """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -318,7 +318,7 @@ def test_code_interpreter_with_conversation(self, **kwargs): - Variables/state persistence across turns """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, @@ -403,7 +403,7 @@ def test_code_interpreter_with_file_in_conversation(self, **kwargs): - Server-side code execution with file access and chart generation """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") import os with ( diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py index 9a8f616e9d7f..084785bc53f3 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py @@ -38,7 +38,7 @@ def test_agent_web_search(self, **kwargs): DELETE /agents/{agent_name}/versions/{agent_version} project_client.agents.delete_version() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") with ( self.create_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py index e11732ca4cac..7aac8aef6977 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py @@ -18,7 +18,7 @@ class TestAgentWebSearchAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_agent_web_search_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") async with ( self.create_async_client(operation_group="agents", **kwargs) as project_client, diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py index 9e790b1f37c7..634076e78aa6 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py @@ -137,7 +137,7 @@ def test_datasets_upload_file(self, **kwargs): @recorded_by_proxy def test_datasets_upload_folder(self, **kwargs): - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("foundry_project_endpoint") print("\n=====> Endpoint:", endpoint) dataset_name = self.test_datasets_params["dataset_name_2"] diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py index 724b6318b938..3e53e4b3d9ed 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py @@ -138,7 +138,7 @@ async def test_datasets_upload_file(self, **kwargs): @recorded_by_proxy_async async def test_datasets_upload_folder_async(self, **kwargs): - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("foundry_project_endpoint") print("\n=====> Endpoint:", endpoint) dataset_name = self.test_datasets_params["dataset_name_4"] diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py index 53132a89a396..c2345115eb92 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py @@ -18,8 +18,8 @@ class TestDeployments(TestBase): def test_deployments(self, **kwargs): model_publisher = "OpenAI" - model_name = kwargs.get("azure_ai_model_deployment_name") - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_name = kwargs.get("foundry_model_name") + model_deployment_name = kwargs.get("foundry_model_name") with self.create_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py index 06f229c1e15b..b278909621e4 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py @@ -18,8 +18,8 @@ class TestDeploymentsAsync(TestBase): async def test_deployments_async(self, **kwargs): model_publisher = "OpenAI" - model_name = kwargs.get("azure_ai_model_deployment_name") - model_deployment_name = kwargs.get("azure_ai_model_deployment_name") + model_name = kwargs.get("foundry_model_name") + model_deployment_name = kwargs.get("foundry_model_name") async with self.create_async_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index aeb407b0e5f0..cdf1b9f4ceda 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -334,11 +334,11 @@ def _test_deploy_and_infer_helper( subscription_id = kwargs.get("azure_subscription_id") resource_group = kwargs.get("azure_resource_group") - project_endpoint = kwargs.get("azure_ai_project_endpoint") + project_endpoint = kwargs.get("foundry_project_endpoint") if not all([subscription_id, resource_group, project_endpoint]): pytest.skip( - f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, azure_ai_project_endpoint) - skipping {test_prefix} deploy and infer test" + f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, foundry_project_endpoint) - skipping {test_prefix} deploy and infer test" ) account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index b9789e077e92..bd4e29481dbe 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -345,11 +345,11 @@ async def _test_deploy_and_infer_helper_async( subscription_id = kwargs.get("azure_subscription_id") resource_group = kwargs.get("azure_resource_group") - project_endpoint = kwargs.get("azure_ai_project_endpoint") + project_endpoint = kwargs.get("foundry_project_endpoint") if not all([subscription_id, resource_group, project_endpoint]): pytest.skip( - f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, azure_ai_project_endpoint) - skipping {test_prefix} deploy and infer test" + f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, foundry_project_endpoint) - skipping {test_prefix} deploy and infer test" ) account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py index 5165f37ddb03..9ef5758e06f3 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py @@ -57,7 +57,7 @@ def test_responses(self, **kwargs): ------+---------------------------------------------+----------------------------------- POST /openai/responses client.responses.create() """ - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") client = self.create_client(operation_group="agents", **kwargs).get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py index bf7252962dad..67eb01609039 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py @@ -45,7 +45,7 @@ class TestResponsesAsync(TestBase): @recorded_by_proxy_async(RecordedTransport.HTTPX) async def test_responses_async(self, **kwargs): - model = kwargs.get("azure_ai_model_deployment_name") + model = kwargs.get("foundry_model_name") client = self.create_async_client(operation_group="agents", **kwargs).get_openai_client() diff --git a/sdk/ai/azure-ai-projects/tests/samples/README.md b/sdk/ai/azure-ai-projects/tests/samples/README.md index 3296cb7c58ea..981ec1155225 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/README.md +++ b/sdk/ai/azure-ai-projects/tests/samples/README.md @@ -67,7 +67,7 @@ class TestSamples(AzureRecordedTestCase): executor.execute() executor.validate_print_calls_by_llm( instructions=agent_tools_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], + project_endpoint=kwargs["foundry_project_endpoint"], ) ``` @@ -106,7 +106,7 @@ class TestSamplesAsync(AzureRecordedTestCase): await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], + project_endpoint=kwargs["foundry_project_endpoint"], ) ``` @@ -122,8 +122,8 @@ from devtools_testutils import EnvironmentVariableLoader servicePreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="gpt-4o", + foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + foundry_model_name="gpt-4o", # add other sanitized vars here ) ``` @@ -154,8 +154,8 @@ If you need to remap the values provided by your fixtures to the environment-var ```python env_vars = { - "AZURE_AI_PROJECT_ENDPOINT": kwargs["TEST_AZURE_AI_PROJECT_ENDPOINT"], - "AZURE_AI_MODEL_DEPLOYMENT_NAME": kwargs["TEST_AZURE_AI_MODEL_DEPLOYMENT_NAME"], + "FOUNDRY_PROJECT_ENDPOINT": kwargs["TEST_FOUNDRY_PROJECT_ENDPOINT"], + "FOUNDRY_MODEL_NAME": kwargs["TEST_FOUNDRY_MODEL_NAME"], } executor = SyncSampleExecutor(self, sample_path, env_vars=env_vars, **kwargs) ``` diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 2bd4594c843d..494a47c1a3a2 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -62,8 +62,8 @@ def test_agent_tools_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -86,8 +86,8 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=memories_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -106,8 +106,8 @@ def test_agents_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=agents_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -128,8 +128,8 @@ def test_connections_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -148,8 +148,8 @@ def test_files_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -168,8 +168,8 @@ def test_deployments_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -188,8 +188,8 @@ def test_datasets_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -211,6 +211,6 @@ def test_finetuning_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=fine_tuning_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py index 8eccef50195f..ab25f7e1b48a 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py @@ -50,8 +50,8 @@ async def test_agent_tools_samples_async(self, sample_path: str, **kwargs) -> No await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -75,8 +75,8 @@ async def test_memory_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=memories_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -95,8 +95,8 @@ async def test_agents_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agents_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -117,8 +117,8 @@ async def test_connections_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -139,8 +139,8 @@ async def test_files_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -159,8 +159,8 @@ async def test_deployments_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -184,6 +184,6 @@ async def test_datasets_samples(self, sample_path: str, **kwargs) -> None: # Proxy server probably not able to parse the captured print content await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index bcea91df0eb5..9f166609aa0d 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -19,9 +19,9 @@ evaluationsPreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="sanitized-model-deployment-name", - azure_ai_agent_name="sanitized-agent-name", + foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + foundry_model_name="sanitized-model-deployment-name", + foundry_agent_name="sanitized-agent-name", ) evaluations_instructions = """ @@ -184,8 +184,8 @@ def test_evaluation_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) # To run this test with a specific sample, use: @@ -216,8 +216,8 @@ def test_agentic_evaluator_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) # To run this test, use: @@ -247,6 +247,6 @@ def test_generic_agentic_evaluator_sample(self, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["azure_ai_project_endpoint"], - model=kwargs["azure_ai_model_deployment_name"], + project_endpoint=kwargs["foundry_project_endpoint"], + model=kwargs["foundry_model_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index ac072e7f5844..74fa089c10c2 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -41,8 +41,8 @@ servicePreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="sanitized-model-deployment-name", + foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + foundry_model_name="sanitized-model-deployment-name", image_generation_model_deployment_name="sanitized-gpt-image", bing_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-bing-connection", ai_search_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-ai-search-connection", @@ -75,8 +75,8 @@ fineTuningServicePreparer = functools.partial( EnvironmentVariableLoader, "", - azure_ai_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", - azure_ai_model_deployment_name="sanitized-model-deployment-name", + foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + foundry_model_name="sanitized-model-deployment-name", azure_ai_projects_azure_subscription_id="00000000-0000-0000-0000-000000000000", azure_ai_projects_azure_resource_group="sanitized-resource-group", azure_ai_projects_azure_aoai_account="sanitized-aoai-account", @@ -297,7 +297,7 @@ def open_with_lf( # helper function: create projects client using environment variables def create_client(self, *, allow_preview: bool = False, **kwargs) -> AIProjectClient: # fetch environment variables - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("foundry_project_endpoint") credential = self.get_credential(AIProjectClient, is_async=False) print(f"Creating AIProjectClient with endpoint: {endpoint}") @@ -314,7 +314,7 @@ def create_client(self, *, allow_preview: bool = False, **kwargs) -> AIProjectCl # helper function: create async projects client using environment variables def create_async_client(self, *, allow_preview: bool = False, **kwargs) -> AsyncAIProjectClient: # fetch environment variables - endpoint = kwargs.pop("azure_ai_project_endpoint") + endpoint = kwargs.pop("foundry_project_endpoint") credential = self.get_credential(AsyncAIProjectClient, is_async=True) print(f"Creating AsyncAIProjectClient with endpoint: {endpoint}") From c497c29c52f99b58939b56c987e419a98622c3c7 Mon Sep 17 00:00:00 2001 From: aprilk-ms <55356546+aprilk-ms@users.noreply.github.com> Date: Wed, 11 Mar 2026 01:04:18 -0700 Subject: [PATCH 11/36] Add CSV and synthetic data generation evaluation samples (#45603) * Add CSV and synthetic data generation evaluation samples Add two new evaluation samples under sdk/ai/azure-ai-projects/samples/evaluations/: - sample_evaluations_builtin_with_csv.py: Demonstrates evaluating pre-computed responses from a CSV file using the csv data source type. Uploads a CSV file via the datasets API, runs coherence/violence/f1 evaluators, and polls results. - sample_synthetic_data_evaluation.py: Demonstrates synthetic data evaluation (preview) that generates test queries from a prompt, sends them to a model target, and evaluates responses with coherence/violence evaluators. Also adds: - data_folder/sample_data_evaluation.csv: Sample CSV data file with 3 rows - README.md: Updated sample index with both new samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update synthetic eval sample: agent target + cleaner dataset ID retrieval - Switch from model target to agent target (azure_ai_agent) - Create agent version via agents.create_version() before evaluation - Simplify output_dataset_id retrieval using getattr instead of nested hasattr/isinstance checks - Add AZURE_AI_AGENT_NAME env var requirement - Remove input_messages (not needed for agent target) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add model target synthetic eval sample, cross-reference both - Add sample_synthetic_data_model_evaluation.py for model target with input_messages system prompt - Update sample_synthetic_data_evaluation.py docstring with cross-reference - Update README.md with both synthetic samples (agent and model) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename synthetic agent sample, clarify README, add prompt/files comments - Rename sample_synthetic_data_evaluation.py to sample_synthetic_data_agent_evaluation.py - Clarify README: JSONL dataset vs CSV dataset descriptions - Remove (preview) from synthetic sample descriptions in README - Add comments about prompt and reference_files options in both synthetic samples Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Skip new eval samples in recording tests Add sample_evaluations_builtin_with_csv.py, sample_synthetic_data_agent_evaluation.py, and sample_synthetic_data_model_evaluation.py to samples_to_skip list since they require file upload prerequisites or are long-running preview features. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename env vars per PR review: FOUNDRY_PROJECT_ENDPOINT, FOUNDRY_MODEL_NAME Address review comments from howieleung: - AZURE_AI_PROJECT_ENDPOINT -> FOUNDRY_PROJECT_ENDPOINT - AZURE_AI_MODEL_DEPLOYMENT_NAME -> FOUNDRY_MODEL_NAME Updated in all 3 new samples and README. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Rename AZURE_AI_AGENT_NAME to FOUNDRY_AGENT_NAME per review Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update changelog with new sample entries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- sdk/ai/azure-ai-projects/CHANGELOG.md | 2 + .../samples/evaluations/README.md | 5 +- .../data_folder/sample_data_evaluation.csv | 4 + .../sample_evaluations_builtin_with_csv.py | 145 +++++++++++++++ .../sample_synthetic_data_agent_evaluation.py | 168 +++++++++++++++++ .../sample_synthetic_data_model_evaluation.py | 171 ++++++++++++++++++ .../tests/samples/test_samples_evaluations.py | 3 + 7 files changed, 497 insertions(+), 1 deletion(-) create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/data_folder/sample_data_evaluation.csv create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 44dfff20303b..6f22f74efb00 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -19,6 +19,8 @@ * Renamed environment variable `AZURE_AI_PROJECT_ENDPOINT` to `FOUNDRY_PROJECT_ENDPOINT` in all samples. * Renamed environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` to `FOUNDRY_MODEL_NAME` in all samples. * Renamed environment variable `AZURE_AI_MODEL_AGENT_NAME` to `FOUNDRY_AGENT_NAME` in all samples. +* Added CSV evaluation sample (`sample_evaluations_builtin_with_csv.py`) demonstrating evaluation with an uploaded CSV dataset. +* Added synthetic data evaluation samples (`sample_synthetic_data_agent_evaluation.py`) and (`sample_synthetic_data_model_evaluation.py`). ### Other Changes diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/README.md b/sdk/ai/azure-ai-projects/samples/evaluations/README.md index b87c5f25d0b5..950aac77e922 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/README.md +++ b/sdk/ai/azure-ai-projects/samples/evaluations/README.md @@ -21,7 +21,8 @@ Set these environment variables: | Sample | Description | |--------|-------------| | [sample_evaluations_builtin_with_inline_data.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py) | Basic evaluation with built-in evaluators using inline data | -| [sample_evaluations_builtin_with_dataset_id.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py) | Evaluate using an uploaded dataset | +| [sample_evaluations_builtin_with_dataset_id.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py) | Evaluate using an uploaded JSONL dataset | +| [sample_evaluations_builtin_with_csv.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py) | Evaluate using an uploaded CSV dataset | | [sample_eval_catalog.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py) | Browse and use evaluators from the evaluation catalog | ### Agent / Model Evaluation @@ -32,6 +33,8 @@ Set these environment variables: | [sample_agent_response_evaluation.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py) | Evaluate given agent responses | | [sample_agent_response_evaluation_with_function_tool.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py) | Evaluate agent responses with function tools | | [sample_model_evaluation.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py) | Create response from model and evaluate | +| [sample_synthetic_data_agent_evaluation.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py) | Generate synthetic test data, evaluate a Foundry agent | +| [sample_synthetic_data_model_evaluation.py](https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py) | Generate synthetic test data, evaluate a model | ### Red Team Evaluations diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/data_folder/sample_data_evaluation.csv b/sdk/ai/azure-ai-projects/samples/evaluations/data_folder/sample_data_evaluation.csv new file mode 100644 index 000000000000..6dd3f1d1c556 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/data_folder/sample_data_evaluation.csv @@ -0,0 +1,4 @@ +query,response,context,ground_truth +What is the capital of France?,Paris is the capital of France.,France is a country in Western Europe.,Paris is the capital of France. +What is machine learning?,Machine learning is a subset of AI that learns from data.,Machine learning is a branch of artificial intelligence.,Machine learning is a type of AI that enables computers to learn from data without being explicitly programmed. +Explain neural networks.,Neural networks are computing systems inspired by biological neural networks.,Neural networks are used in deep learning.,Neural networks are a set of algorithms modeled after the human brain designed to recognize patterns. diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py new file mode 100644 index 000000000000..6259ad3aac5c --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py @@ -0,0 +1,145 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + Given an AIProjectClient, this sample demonstrates how to use the synchronous + `openai.evals.*` methods to create, get and list evaluation and eval runs + using a CSV file uploaded as a dataset. + + Unlike JSONL-based evaluations, this sample uses the `csv` data source type + to run evaluations directly against a CSV file. + +USAGE: + python sample_evaluations_builtin_with_csv.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. + 3) DATASET_NAME - Optional. The name of the Dataset to create and use in this sample. + 4) DATASET_VERSION - Optional. The version of the Dataset to create and use in this sample. + 5) DATA_FOLDER - Optional. The folder path where the data files for upload are located. +""" + +import os + +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient + +import time +from pprint import pprint +from openai.types.eval_create_params import DataSourceConfigCustom +from azure.ai.projects.models import ( + DatasetVersion, +) +from dotenv import load_dotenv +from datetime import datetime, timezone + +load_dotenv() + + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") +dataset_name = os.environ.get("DATASET_NAME", "") +dataset_version = os.environ.get("DATASET_VERSION", "1") + +# Construct the paths to the data folder and CSV data file used in this sample +script_dir = os.path.dirname(os.path.abspath(__file__)) +data_folder = os.environ.get("DATA_FOLDER", os.path.join(script_dir, "data_folder")) +data_file = os.path.join(data_folder, "sample_data_evaluation.csv") + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as client, +): + + print("Upload a CSV file and create a new Dataset to reference the file.") + dataset: DatasetVersion = project_client.datasets.upload_file( + name=dataset_name or f"eval-csv-data-{datetime.now(timezone.utc).strftime('%Y-%m-%d_%H%M%S_UTC')}", + version=dataset_version, + file_path=data_file, + ) + pprint(dataset) + + data_source_config = DataSourceConfigCustom( + { + "type": "custom", + "item_schema": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "response": {"type": "string"}, + "context": {"type": "string"}, + "ground_truth": {"type": "string"}, + }, + "required": [], + }, + "include_sample_schema": True, + } + ) + + testing_criteria = [ + { + "type": "azure_ai_evaluator", + "name": "violence", + "evaluator_name": "builtin.violence", + "data_mapping": {"query": "{{item.query}}", "response": "{{item.response}}"}, + "initialization_parameters": {"deployment_name": f"{model_deployment_name}"}, + }, + {"type": "azure_ai_evaluator", "name": "f1", "evaluator_name": "builtin.f1_score"}, + { + "type": "azure_ai_evaluator", + "name": "coherence", + "evaluator_name": "builtin.coherence", + "data_mapping": {"query": "{{item.query}}", "response": "{{item.response}}"}, + "initialization_parameters": {"deployment_name": f"{model_deployment_name}"}, + }, + ] + + print("Creating evaluation") + eval_object = client.evals.create( + name="CSV evaluation with built-in evaluators", + data_source_config=data_source_config, # type: ignore + testing_criteria=testing_criteria, # type: ignore + ) + print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") + + print("Creating evaluation run with CSV data source") + eval_run_object = client.evals.runs.create( + eval_id=eval_object.id, + name="csv_evaluation_run", + metadata={"team": "eval-exp", "scenario": "csv-eval-v1"}, + data_source={ # type: ignore + "type": "csv", + "source": { + "type": "file_id", + "id": dataset.id if dataset.id else "", + }, + }, + ) + + print(f"Evaluation run created (id: {eval_run_object.id})") + pprint(eval_run_object) + + while True: + run = client.evals.runs.retrieve(run_id=eval_run_object.id, eval_id=eval_object.id) + if run.status in ["completed", "failed"]: + output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) + pprint(output_items) + print(f"Eval Run Report URL: {run.report_url}") + + break + time.sleep(5) + print("Waiting for evaluation run to complete...") + + client.evals.delete(eval_id=eval_object.id) + print("Evaluation deleted") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py new file mode 100644 index 000000000000..c67840c6325d --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py @@ -0,0 +1,168 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to create and run a synthetic data evaluation + against a Foundry agent using the synchronous AIProjectClient. + + Synthetic data evaluation generates test queries based on a prompt you provide, + sends them to a Foundry agent, and evaluates the responses — no pre-existing + test dataset required. The generated queries are stored as a dataset in your + project for reuse. + + For evaluating a deployed model instead of an agent, see + sample_synthetic_data_model_evaluation.py. + + This feature is currently in preview. + +USAGE: + python sample_synthetic_data_agent_evaluation.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for generating + synthetic data and for AI-assisted evaluators. + 3) FOUNDRY_AGENT_NAME - Required. The name of the Foundry agent to evaluate. +""" + +import os +import time +from pprint import pprint +from typing import Union +from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition +from openai.types.evals.run_create_response import RunCreateResponse +from openai.types.evals.run_retrieve_response import RunRetrieveResponse + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] +agent_name = os.environ["FOUNDRY_AGENT_NAME"] + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as client, +): + # Create (or update) an agent version to evaluate + agent = project_client.agents.create_version( + agent_name=agent_name, + definition=PromptAgentDefinition( + model=model_deployment_name, + instructions="You are a helpful customer service agent. Be empathetic and solution-oriented.", + ), + ) + print(f"Agent created (name: {agent.name}, version: {agent.version})") + + # Use the azure_ai_source data source config with the synthetic_data_gen_preview scenario. + # The schema is inferred from the service — no custom item_schema is needed. + data_source_config = {"type": "azure_ai_source", "scenario": "synthetic_data_gen_preview"} + + # Define testing criteria using builtin evaluators. + # {{item.query}} references the synthetically generated query. + # {{sample.output_text}} references the agent's plain text response. + testing_criteria = [ + { + "type": "azure_ai_evaluator", + "name": "coherence", + "evaluator_name": "builtin.coherence", + "initialization_parameters": { + "deployment_name": model_deployment_name, + }, + "data_mapping": { + "query": "{{item.query}}", + "response": "{{sample.output_text}}", + }, + }, + { + "type": "azure_ai_evaluator", + "name": "violence", + "evaluator_name": "builtin.violence", + "data_mapping": { + "query": "{{item.query}}", + "response": "{{sample.output_text}}", + }, + }, + ] + + print("Creating evaluation for synthetic data generation") + eval_object = client.evals.create( + name="Synthetic Data Evaluation", + data_source_config=data_source_config, # type: ignore + testing_criteria=testing_criteria, # type: ignore + ) + print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") + + # Configure the synthetic data generation data source with an agent target. + # The service generates queries based on the prompt, sends them to the agent, + # and evaluates the responses. + # + # You can guide query generation in two ways: + # - "prompt": A text description of the queries to generate (used below). + # - "reference_files": A list of dataset asset IDs (uploaded via the datasets API) + # in the format of 'azureai://accounts//projects//data//versions/' + # whose content the service uses as context for generating queries. + # You can use either or both together. + data_source = { + "type": "azure_ai_synthetic_data_gen_preview", + "item_generation_params": { + "type": "synthetic_data_gen_preview", + "samples_count": 5, + "prompt": "Generate customer service questions about returning defective products", + # "reference_files": ["", ""], + "model_deployment_name": model_deployment_name, + "output_dataset_name": "synthetic-eval-dataset", + }, + "target": { + "type": "azure_ai_agent", + "name": agent.name, + "version": agent.version, + }, + } + + eval_run: Union[RunCreateResponse, RunRetrieveResponse] = client.evals.runs.create( + eval_id=eval_object.id, + name="synthetic-data-evaluation-run", + data_source=data_source, # type: ignore + ) + print(f"Evaluation run created (id: {eval_run.id})") + + while eval_run.status not in ["completed", "failed"]: + eval_run = client.evals.runs.retrieve(run_id=eval_run.id, eval_id=eval_object.id) + print(f"Waiting for eval run to complete... current status: {eval_run.status}") + time.sleep(5) + + if eval_run.status == "completed": + print("\n✓ Evaluation run completed successfully!") + print(f"Result Counts: {eval_run.result_counts}") + + output_items = list(client.evals.runs.output_items.list(run_id=eval_run.id, eval_id=eval_object.id)) + print(f"\nOUTPUT ITEMS (Total: {len(output_items)})") + print(f"{'-'*60}") + pprint(output_items) + print(f"{'-'*60}") + + print(f"\nEval Run Report URL: {eval_run.report_url}") + + # The synthetic data generation run stores the generated queries as a dataset. + # Retrieve the output dataset ID from the run's data_source for reuse. + output_dataset_id = getattr(eval_run.data_source, "item_generation_params", {}).get("output_dataset_id") + if output_dataset_id: + print(f"Output Dataset ID (for reuse): {output_dataset_id}") + else: + print("\n✗ Evaluation run failed.") + + client.evals.delete(eval_id=eval_object.id) + print("Evaluation deleted") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py new file mode 100644 index 000000000000..7bf1cc1a2e77 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py @@ -0,0 +1,171 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to create and run a synthetic data evaluation + against a deployed model using the synchronous AIProjectClient. + + Synthetic data evaluation generates test queries based on a prompt you provide, + sends them to a deployed model, and evaluates the responses — no pre-existing + test dataset required. The generated queries are stored as a dataset in your + project for reuse. + + For evaluating a Foundry agent instead of a model, see + sample_synthetic_data_agent_evaluation.py. + + This feature is currently in preview. + +USAGE: + python sample_synthetic_data_model_evaluation.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. + 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for generating + synthetic data and as the evaluation target. +""" + +import os +import time +from pprint import pprint +from typing import Union +from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from openai.types.evals.run_create_response import RunCreateResponse +from openai.types.evals.run_retrieve_response import RunRetrieveResponse + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] + +with( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as client, +): + # Use the azure_ai_source data source config with the synthetic_data_gen_preview scenario. + # The schema is inferred from the service — no custom item_schema is needed. + data_source_config = {"type": "azure_ai_source", "scenario": "synthetic_data_gen_preview"} + + # Define testing criteria using builtin evaluators. + # {{item.query}} references the synthetically generated query. + # {{sample.output_text}} references the model's response. + testing_criteria = [ + { + "type": "azure_ai_evaluator", + "name": "coherence", + "evaluator_name": "builtin.coherence", + "initialization_parameters": { + "deployment_name": model_deployment_name, + }, + "data_mapping": { + "query": "{{item.query}}", + "response": "{{sample.output_text}}", + }, + }, + { + "type": "azure_ai_evaluator", + "name": "violence", + "evaluator_name": "builtin.violence", + "data_mapping": { + "query": "{{item.query}}", + "response": "{{sample.output_text}}", + }, + }, + ] + + print("Creating evaluation for synthetic data generation") + eval_object = client.evals.create( + name="Synthetic Data Model Evaluation", + data_source_config=data_source_config, # type: ignore + testing_criteria=testing_criteria, # type: ignore + ) + print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") + + # Configure the synthetic data generation data source with a model target. + # The service generates queries based on the prompt, sends them to the model, + # and evaluates the responses. + # + # You can guide query generation in two ways: + # - "prompt": A text description of the queries to generate (used below). + # - "reference_files": A list of dataset asset IDs (uploaded via the datasets API) + # in the format of 'azureai://accounts//projects//data//versions/' + # whose content the service uses as context for generating queries. + # You can use either or both together. + data_source = { + "type": "azure_ai_synthetic_data_gen_preview", + "item_generation_params": { + "type": "synthetic_data_gen_preview", + "samples_count": 5, + "prompt": "Generate customer service questions about returning defective products", + # "reference_files": ["", ""], + "model_deployment_name": model_deployment_name, + "output_dataset_name": "synthetic-model-eval-dataset", + }, + "target": { + "type": "azure_ai_model", + "model": model_deployment_name, + }, + # Optional: add a system prompt to shape the target model's behavior. + # When using input_messages with synthetic data generation, include only + # system/developer role messages — the service provides the generated queries + # as user messages automatically. + "input_messages": { + "type": "template", + "template": [ + { + "type": "message", + "role": "developer", + "content": { + "type": "input_text", + "text": "You are a helpful customer service agent. Be empathetic and solution-oriented.", + }, + } + ], + }, + } + + eval_run: Union[RunCreateResponse, RunRetrieveResponse] = client.evals.runs.create( + eval_id=eval_object.id, + name="synthetic-data-model-evaluation-run", + data_source=data_source, # type: ignore + ) + print(f"Evaluation run created (id: {eval_run.id})") + + while eval_run.status not in ["completed", "failed"]: + eval_run = client.evals.runs.retrieve(run_id=eval_run.id, eval_id=eval_object.id) + print(f"Waiting for eval run to complete... current status: {eval_run.status}") + time.sleep(5) + + if eval_run.status == "completed": + print("\n✓ Evaluation run completed successfully!") + print(f"Result Counts: {eval_run.result_counts}") + + output_items = list(client.evals.runs.output_items.list(run_id=eval_run.id, eval_id=eval_object.id)) + print(f"\nOUTPUT ITEMS (Total: {len(output_items)})") + print(f"{'-'*60}") + pprint(output_items) + print(f"{'-'*60}") + + print(f"\nEval Run Report URL: {eval_run.report_url}") + + # The synthetic data generation run stores the generated queries as a dataset. + # Retrieve the output dataset ID from the run's data_source for reuse. + output_dataset_id = getattr(eval_run.data_source, "item_generation_params", {}).get("output_dataset_id") + if output_dataset_id: + print(f"Output Dataset ID (for reuse): {output_dataset_id}") + else: + print("\n✗ Evaluation run failed.") + + client.evals.delete(eval_id=eval_object.id) + print("Evaluation deleted") diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index 9f166609aa0d..a1d3a68a0b9b 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -167,6 +167,9 @@ class TestSamplesEvaluations(AzureRecordedTestCase): "sample_scheduled_evaluations.py", # Missing dependency azure.mgmt.resource (ModuleNotFoundError) "sample_evaluations_builtin_with_dataset_id.py", # Requires dataset upload / Blob Storage prerequisite "sample_continuous_evaluation_rule.py", # Requires manual RBAC assignment in Azure Portal + "sample_evaluations_builtin_with_csv.py", # Requires CSV file upload prerequisite + "sample_synthetic_data_agent_evaluation.py", # Synthetic data gen is long-running preview feature + "sample_synthetic_data_model_evaluation.py", # Synthetic data gen is long-running preview feature ], ), ) From db3ea9d9c686648fc0176f83779535209eb9814c Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Wed, 11 Mar 2026 16:02:18 -0700 Subject: [PATCH 12/36] Fix azure-ai-projects linting errors with pylint version 4.0.5 (#45628) --- sdk/ai/azure-ai-projects/README.md | 17 +- sdk/ai/azure-ai-projects/assets.json | 2 +- .../azure/ai/projects/_patch.py | 2 +- .../azure/ai/projects/aio/_patch.py | 2 +- sdk/ai/azure-ai-projects/dev_requirements.txt | 13 +- .../samples/agents/agent_retrieve_helper.py | 4 +- .../samples/agents/sample_agent_basic.py | 2 +- .../agents/sample_agent_basic_async.py | 2 +- .../agents/sample_agent_retrieve_basic.py | 4 +- .../sample_agent_retrieve_basic_async.py | 4 +- .../agents/sample_agent_stream_events.py | 4 +- .../agents/sample_agent_structured_output.py | 2 +- .../sample_agent_structured_output_async.py | 2 +- .../agents/sample_workflow_multi_agent.py | 16 +- .../sample_workflow_multi_agent_async.py | 18 +-- ...sample_agent_basic_with_console_tracing.py | 5 +- ..._with_console_tracing_custom_attributes.py | 1 + .../samples/agents/tools/computer_use_util.py | 1 + .../agents/tools/sample_agent_ai_search.py | 4 +- .../tools/sample_agent_bing_custom_search.py | 6 +- .../tools/sample_agent_bing_grounding.py | 4 +- .../tools/sample_agent_browser_automation.py | 8 +- .../tools/sample_agent_code_interpreter.py | 2 +- ...ample_agent_code_interpreter_with_files.py | 4 +- .../agents/tools/sample_agent_computer_use.py | 14 +- .../tools/sample_agent_computer_use_async.py | 13 +- .../agents/tools/sample_agent_fabric.py | 4 +- .../agents/tools/sample_agent_file_search.py | 7 +- .../sample_agent_file_search_in_stream.py | 18 +-- ...ample_agent_file_search_in_stream_async.py | 17 +- .../tools/sample_agent_function_tool.py | 7 +- .../tools/sample_agent_function_tool_async.py | 7 +- .../samples/agents/tools/sample_agent_mcp.py | 4 +- .../agents/tools/sample_agent_mcp_async.py | 2 +- ...ample_agent_mcp_with_project_connection.py | 4 +- ...agent_mcp_with_project_connection_async.py | 2 +- .../agents/tools/sample_agent_openapi.py | 4 +- ...e_agent_openapi_with_project_connection.py | 2 +- .../agents/tools/sample_agent_sharepoint.py | 6 +- .../agents/tools/sample_agent_to_agent.py | 4 +- .../agents/tools/sample_agent_web_search.py | 4 +- .../tools/sample_agent_web_search_preview.py | 4 +- ...ple_agent_web_search_with_custom_search.py | 4 +- .../agentic_evaluators/sample_coherence.py | 14 +- .../agentic_evaluators/sample_fluency.py | 14 +- .../agent_utils.py | 13 +- .../sample_generic_agentic_evaluator.py | 12 +- .../agentic_evaluators/sample_groundedness.py | 16 +- .../sample_intent_resolution.py | 14 +- .../agentic_evaluators/sample_relevance.py | 14 +- .../sample_response_completeness.py | 14 +- .../sample_task_adherence.py | 14 +- .../sample_task_completion.py | 14 +- .../sample_task_navigation_efficiency.py | 14 +- .../sample_tool_call_accuracy.py | 14 +- .../sample_tool_call_success.py | 15 +- .../sample_tool_input_accuracy.py | 14 +- .../sample_tool_output_utilization.py | 14 +- .../sample_tool_selection.py | 14 +- .../evaluations/sample_agent_evaluation.py | 6 +- .../sample_agent_response_evaluation.py | 4 +- ..._response_evaluation_with_function_tool.py | 7 +- .../sample_continuous_evaluation_rule.py | 4 +- .../evaluations/sample_eval_catalog.py | 13 +- ...mple_eval_catalog_code_based_evaluators.py | 16 +- ...le_eval_catalog_prompt_based_evaluators.py | 28 ++-- .../sample_evaluation_cluster_insight.py | 18 +-- .../sample_evaluation_compare_insight.py | 12 +- .../sample_evaluations_ai_assisted.py | 11 +- .../sample_evaluations_builtin_with_csv.py | 12 +- ...ple_evaluations_builtin_with_dataset_id.py | 12 +- ...le_evaluations_builtin_with_inline_data.py | 13 +- ...valuations_builtin_with_inline_data_oai.py | 12 +- .../sample_evaluations_builtin_with_traces.py | 8 +- .../evaluations/sample_evaluations_graders.py | 9 +- ...aluations_score_model_grader_with_image.py | 16 +- .../evaluations/sample_model_evaluation.py | 4 +- .../evaluations/sample_redteam_evaluations.py | 15 +- .../sample_scheduled_evaluations.py | 29 ++-- .../sample_synthetic_data_agent_evaluation.py | 6 +- .../sample_synthetic_data_model_evaluation.py | 6 +- .../samples/files/sample_files.py | 5 +- .../samples/files/sample_files_async.py | 3 +- .../finetuning/sample_finetuning_dpo_job.py | 2 +- .../sample_finetuning_dpo_job_async.py | 2 +- ...le_finetuning_oss_models_supervised_job.py | 2 +- ...etuning_oss_models_supervised_job_async.py | 2 +- .../sample_finetuning_reinforcement_job.py | 2 +- ...mple_finetuning_reinforcement_job_async.py | 2 +- .../sample_finetuning_supervised_job.py | 5 +- .../sample_finetuning_supervised_job_async.py | 6 +- .../mcp_client/sample_mcp_tool_async.py | 6 +- .../memories/sample_memory_advanced.py | 2 +- .../memories/sample_memory_advanced_async.py | 2 +- .../samples/red_team/sample_red_team_async.py | 1 - .../sample_responses_stream_events.py | 4 +- .../sample_responses_stream_manager.py | 4 +- .../sample_responses_structured_output.py | 2 +- .../agents/telemetry/gen_ai_trace_verifier.py | 17 +- .../agents/telemetry/memory_trace_exporter.py | 4 +- .../telemetry/test_ai_agents_instrumentor.py | 54 ++----- .../test_ai_agents_instrumentor_async.py | 40 ++--- .../telemetry/test_ai_instrumentor_base.py | 10 +- .../telemetry/test_responses_instrumentor.py | 147 +++++++++--------- .../test_responses_instrumentor_async.py | 75 ++++----- ...sponses_instrumentor_browser_automation.py | 37 +++-- ...s_instrumentor_browser_automation_async.py | 42 +++-- ...responses_instrumentor_code_interpreter.py | 51 +++--- ...ses_instrumentor_code_interpreter_async.py | 53 ++++--- ...test_responses_instrumentor_file_search.py | 47 +++--- ...esponses_instrumentor_file_search_async.py | 49 +++--- .../test_responses_instrumentor_mcp.py | 47 +++--- .../test_responses_instrumentor_mcp_async.py | 49 +++--- .../test_responses_instrumentor_metrics.py | 12 +- .../test_responses_instrumentor_workflow.py | 75 +++++---- ...t_responses_instrumentor_workflow_async.py | 80 +++++----- .../test_trace_function_decorator.py | 8 +- .../test_trace_function_decorator_async.py | 16 +- .../test_agent_create_version_exception.py | 1 - .../tests/agents/test_agent_responses_crud.py | 3 +- .../agents/test_agent_responses_crud_async.py | 3 +- .../tests/agents/test_agents_crud.py | 1 - .../tests/agents/test_agents_crud_async.py | 1 - .../tests/agents/test_conversation_crud.py | 3 +- .../agents/test_conversation_crud_async.py | 3 +- .../agents/test_conversation_items_crud.py | 11 +- .../test_conversation_items_crud_async.py | 11 +- ...est_agent_code_interpreter_and_function.py | 4 - ..._agent_file_search_and_code_interpreter.py | 2 - .../test_agent_file_search_and_function.py | 6 +- ...t_file_search_code_interpreter_function.py | 5 - .../test_multitool_with_conversations.py | 7 +- .../agents/tools/test_agent_ai_search.py | 2 +- .../agents/tools/test_agent_bing_grounding.py | 6 +- .../tools/test_agent_code_interpreter.py | 2 +- .../test_agent_code_interpreter_async.py | 1 - .../agents/tools/test_agent_file_search.py | 6 +- .../tools/test_agent_file_search_async.py | 1 - .../tools/test_agent_file_search_stream.py | 1 - .../test_agent_file_search_stream_async.py | 1 - .../agents/tools/test_agent_function_tool.py | 5 +- .../tools/test_agent_function_tool_async.py | 7 +- .../tools/test_agent_image_generation.py | 2 +- .../test_agent_image_generation_async.py | 2 +- .../tests/agents/tools/test_agent_mcp.py | 2 +- .../agents/tools/test_agent_mcp_async.py | 3 +- .../agents/tools/test_agent_memory_search.py | 6 +- .../tools/test_agent_memory_search_async.py | 6 +- .../tests/agents/tools/test_agent_openapi.py | 2 +- .../agents/tools/test_agent_openapi_async.py | 2 +- .../test_agent_tools_with_conversations.py | 5 +- .../agents/tools/test_agent_web_search.py | 1 - .../tools/test_agent_web_search_async.py | 1 - .../tests/connections/test_connections.py | 5 +- .../connections/test_connections_async.py | 4 +- .../tests/datasets/test_datasets.py | 10 +- .../tests/datasets/test_datasets_async.py | 12 +- .../tests/deployments/test_deployments.py | 2 - .../deployments/test_deployments_async.py | 2 - .../tests/files/test_files.py | 2 - .../tests/files/test_files_async.py | 2 - .../tests/finetuning/test_finetuning.py | 14 +- .../tests/finetuning/test_finetuning_async.py | 14 +- .../tests/indexes/test_indexes.py | 3 +- .../tests/indexes/test_indexes_async.py | 3 +- .../tests/redteams/test_redteams.py | 5 +- .../tests/redteams/test_redteams_async.py | 5 +- .../responses/test_openai_client_overrides.py | 2 +- .../test_openai_client_overrides_async.py | 2 +- .../tests/responses/test_responses.py | 6 +- .../tests/responses/test_responses_async.py | 10 +- .../tests/samples/test_samples.py | 3 +- .../tests/samples/test_samples_evaluations.py | 1 + .../tests/telemetry/test_telemetry.py | 2 - .../tests/telemetry/test_telemetry_async.py | 2 - sdk/ai/azure-ai-projects/tests/test_base.py | 29 ++-- 176 files changed, 997 insertions(+), 974 deletions(-) diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 15e6b44c62ef..2b30726f33aa 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -177,7 +177,7 @@ with project_client.get_openai_client() as openai_client: conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": "And what is the capital city?"}], ) - print(f"Added a second user message to the conversation") + print("Added a second user message to the conversation") response = openai_client.responses.create( conversation=conversation.id, @@ -229,7 +229,7 @@ the `code_interpreter_call` output item: ```python code = next((output.code for output in response.output if output.type == "code_interpreter_call"), "") -print(f"Code Interpreter code:") +print("Code Interpreter code:") print(code) ``` @@ -246,7 +246,9 @@ asset_file_path = os.path.abspath( ) # Upload the CSV file for the code interpreter -file = openai_client.files.create(purpose="assistants", file=open(asset_file_path, "rb")) +with open(asset_file_path, "rb") as f: + file = openai_client.files.create(purpose="assistants", file=f) + tool = CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=[file.id])) ``` @@ -273,9 +275,10 @@ print(f"Vector store created (id: {vector_store.id})") asset_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../assets/product_info.md")) # Upload file to vector store -file = openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=open(asset_file_path, "rb") -) +with open(asset_file_path, "rb") as f: + file = openai_client.vector_stores.files.upload_and_poll( + vector_store_id=vector_store.id, file=f + ) print(f"File uploaded to vector store (id: {file.id})") tool = FileSearchTool(vector_store_ids=[vector_store.id]) @@ -415,7 +418,7 @@ Call external APIs defined by OpenAPI specifications without additional client-s ```python -with open(weather_asset_file_path, "r") as f: +with open(weather_asset_file_path, "r", encoding="utf-8") as f: openapi_weather = cast(dict[str, Any], jsonref.loads(f.read())) tool = OpenApiTool( diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 3d798e3359e9..4d184f12bdb4 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_f6ff7973d2" + "Tag": "python/ai/azure-ai-projects_5b25ba9450" } 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 ec64b8f51fc8..98c3e388bb92 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -12,7 +12,7 @@ import re import logging from typing import List, Any -import httpx +import httpx # pylint: disable=networking-import-outside-azure-core-transport from openai import OpenAI from azure.core.tracing.decorator import distributed_trace from azure.core.credentials import TokenCredential 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 4d23bf74a223..837ca0b1942f 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 @@ -11,7 +11,7 @@ import os import logging from typing import List, Any -import httpx +import httpx # pylint: disable=networking-import-outside-azure-core-transport from openai import AsyncOpenAI from azure.core.tracing.decorator import distributed_trace from azure.core.credentials_async import AsyncTokenCredential diff --git a/sdk/ai/azure-ai-projects/dev_requirements.txt b/sdk/ai/azure-ai-projects/dev_requirements.txt index c606cfa5f8d7..3a0781d99156 100644 --- a/sdk/ai/azure-ai-projects/dev_requirements.txt +++ b/sdk/ai/azure-ai-projects/dev_requirements.txt @@ -4,9 +4,16 @@ # pinning remote version due to limitations of azdo feeds with this package https://azuresdkartifacts.z5.web.core.windows.net/python/distributions/distros/opentelemetry_resource_detector_azure-0.1.5-py3-none-any.whl aiohttp -python-dotenv -opentelemetry-sdk azure-core-tracing-opentelemetry -azure-monitor-opentelemetry +azure-mgmt-authorization azure-mgmt-cognitiveservices +azure-mgmt-resource +azure-monitor-opentelemetry +azure-monitor-query jsonref +opentelemetry-sdk +python-dotenv +# Can't include those, because they are not supported in Python 3.9. Samples that use these package +# cannot be run as pytest, because the pipeline will fail on Python 3.9 jobs. +# pillow +# mcp diff --git a/sdk/ai/azure-ai-projects/samples/agents/agent_retrieve_helper.py b/sdk/ai/azure-ai-projects/samples/agents/agent_retrieve_helper.py index 74bfc41cc399..a1efd3b86a52 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/agent_retrieve_helper.py +++ b/sdk/ai/azure-ai-projects/samples/agents/agent_retrieve_helper.py @@ -1,3 +1,4 @@ +# pylint: disable=name-too-long # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -14,10 +15,7 @@ from typing import Generator, AsyncGenerator from azure.ai.projects.models import PromptAgentDefinition - -from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from azure.identity.aio import DefaultAzureCredential as AsyncDefaultAzureCredential from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py index e14dbbcb6148..2f097665de87 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py @@ -67,7 +67,7 @@ conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": "And what is the capital city?"}], ) - print(f"Added a second user message to the conversation") + print("Added a second user message to the conversation") response = openai_client.responses.create( conversation=conversation.id, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py index 009171acdee9..0f9d39bc6685 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py @@ -69,7 +69,7 @@ async def main() -> None: conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": "And what is the capital city?"}], ) - print(f"Added a second user message to the conversation") + print("Added a second user message to the conversation") response = await openai_client.responses.create( conversation=conversation.id, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py index 07d039779a94..c1f456866c9e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic.py @@ -30,9 +30,9 @@ import os from dotenv import load_dotenv +from agent_retrieve_helper import create_and_retrieve_agent_and_conversation # pylint: disable=import-error from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from agent_retrieve_helper import create_and_retrieve_agent_and_conversation load_dotenv() @@ -64,7 +64,7 @@ conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": "How many feet are in a mile?"}], ) - print(f"Added a user message to the conversation") + print("Added a user message to the conversation") response = openai_client.responses.create( conversation=conversation.id, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py index 3f2c79647935..8c6491746f70 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_retrieve_basic_async.py @@ -31,9 +31,9 @@ import os import asyncio from dotenv import load_dotenv +from agent_retrieve_helper import create_and_retrieve_agent_and_conversation_async # pylint: disable=import-error from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient -from agent_retrieve_helper import create_and_retrieve_agent_and_conversation_async load_dotenv() @@ -67,7 +67,7 @@ async def main(): conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": "How many feet are in a mile?"}], ) - print(f"Added a user message to the conversation") + print("Added a user message to the conversation") response = await openai_client.responses.create( conversation=conversation.id, diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py index 990fb833b7e4..417baa7d3ee5 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_stream_events.py @@ -69,9 +69,9 @@ elif event.type == "response.output_text.delta": print(event.delta, end="", flush=True) elif event.type == "response.text.done": - print(f"\n\nResponse text done. Access final text in 'event.text'") + print("\n\nResponse text done. Access final text in 'event.text'") elif event.type == "response.completed": - print(f"\n\nResponse completed. Access final text in 'event.response.output_text'") + print("\n\nResponse completed. Access final text in 'event.response.output_text'") openai_client.conversations.delete(conversation_id=conversation.id) print("Conversation deleted") diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py index b7bd9155561b..5579038bac2c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output.py @@ -32,6 +32,7 @@ import os from dotenv import load_dotenv +from pydantic import BaseModel, Field from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( @@ -39,7 +40,6 @@ PromptAgentDefinitionTextOptions, TextResponseFormatJsonSchema, ) -from pydantic import BaseModel, Field load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py index 48b58d57633c..c4a652cf846b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_structured_output_async.py @@ -33,6 +33,7 @@ import asyncio import os from dotenv import load_dotenv +from pydantic import BaseModel, Field from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient from azure.ai.projects.models import ( @@ -40,7 +41,6 @@ PromptAgentDefinitionTextOptions, TextResponseFormatJsonSchema, ) -from pydantic import BaseModel, Field load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py index 6f3d21766939..f2ce47c55b7c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent.py @@ -47,8 +47,8 @@ agent_name="teacher-agent", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are a teacher that create pre-school math question for student and check answer. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that create pre-school math question for student and check answer. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -59,14 +59,14 @@ agent_name="student-agent", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) print(f"Agent created (id: {student_agent.id}, name: {student_agent.name}, version: {student_agent.version})") # Create Multi-Agent Workflow - workflow_yaml = f""" + workflow_yaml = """ kind: workflow trigger: kind: OnConversationStart @@ -109,7 +109,7 @@ - kind: SendActivity id: send_teacher_reply - activity: "{{Last(Local.LatestMessage).Text}}" + activity: "{{Last(Local.LatestMessage).Text}}" - kind: SetVariable id: set_variable_turncount @@ -158,10 +158,10 @@ for event in stream: print(f"Event {event.sequence_number} type '{event.type}'", end="") if ( - event.type == "response.output_item.added" or event.type == "response.output_item.done" - ) and event.item.type == "workflow_action": + event.type in ("response.output_item.added", "response.output_item.done") + ) and event.item.type == "workflow_action": # pyright: ignore [reportAttributeAccessIssue] print( - f": item action ID '{event.item.action_id}' is '{event.item.status}' (previous action ID: '{event.item.previous_action_id}')", + f": item action ID '{event.item.action_id}' is '{event.item.status}' (previous action ID: '{event.item.previous_action_id}')", # pyright: ignore [reportAttributeAccessIssue] end="", ) elif event.type == "response.completed": diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py index 1d971d0ce3b3..8673b7ac284d 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_async.py @@ -51,8 +51,8 @@ async def main(): agent_name="teacher-agent-async", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are a teacher that create pre-school math question for student and check answer. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that create pre-school math question for student and check answer. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -62,13 +62,13 @@ async def main(): agent_name="student-agent-async", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) print(f"Agent created (id: {student_agent.id}, name: {student_agent.name}, version: {student_agent.version})") - workflow_yaml = f""" + workflow_yaml = """ kind: workflow trigger: kind: OnConversationStart @@ -111,8 +111,8 @@ async def main(): - kind: SendActivity id: send_teacher_reply - activity: "{{Last(Local.LatestMessage).Text}}" - + activity: "{{Last(Local.LatestMessage).Text}}" + - kind: SetVariable id: set_variable_turncount variable: Local.TurnCount @@ -160,10 +160,10 @@ async def main(): async for event in stream: print(f"Event {event.sequence_number} type '{event.type}'", end="") if ( - event.type == "response.output_item.added" or event.type == "response.output_item.done" - ) and event.item.type == "workflow_action": + event.type in ("response.output_item.added", "response.output_item.done") + ) and event.item.type == "workflow_action": # pyright: ignore [reportAttributeAccessIssue] print( - f": item action ID '{event.item.action_id}' is '{event.item.status}' (previous action ID: '{event.item.previous_action_id}')", + f": item action ID '{event.item.action_id}' is '{event.item.status}' (previous action ID: '{event.item.previous_action_id}')", # pyright: ignore [reportAttributeAccessIssue] end="", ) elif event.type == "response.completed": diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py index 82d04285785b..85e1504783d2 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing.py @@ -1,3 +1,4 @@ +# pylint: disable=wrong-import-position,wrong-import-order,docstring-missing-param,ungrouped-imports # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -50,7 +51,7 @@ load_dotenv() -def display_conversation_item(item: Any) -> None: +def display_conversation_item(item: Any) -> None: # pylint: disable=redefined-outer-name """Safely display conversation item information""" print(f"Item ID: {getattr(item, 'id', 'N/A')}") print(f"Type: {getattr(item, 'type', 'N/A')}") @@ -118,7 +119,7 @@ def display_conversation_item(item: Any) -> None: ) print(f"Answer: {response.output}") - print(f"\n📋 Listing conversation items...") + print("\n📋 Listing conversation items...") items = openai_client.conversations.items.list(conversation_id=conversation.id) # Print all the items diff --git a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py index 7fe16305c996..44251d2ae830 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py +++ b/sdk/ai/azure-ai-projects/samples/agents/telemetry/sample_agent_basic_with_console_tracing_custom_attributes.py @@ -1,3 +1,4 @@ +# pylint: disable=wrong-import-position,wrong-import-order,ungrouped-imports # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/computer_use_util.py b/sdk/ai/azure-ai-projects/samples/agents/tools/computer_use_util.py index c1793779b0fe..cdd9c2e21496 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/computer_use_util.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/computer_use_util.py @@ -1,3 +1,4 @@ +# pylint: disable=docstring-missing-param,docstring-missing-return,docstring-missing-rtype,name-too-long # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py index 75ba32b79d8b..f2b9f01ee657 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_ai_search.py @@ -93,7 +93,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -107,7 +107,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Agent response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py index b61867cbe8f2..fd3a6a1910b3 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_custom_search.py @@ -75,7 +75,7 @@ agent_name="MyAgent", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are a helpful agent that can use Bing Custom Search tools to assist users. + instructions="""You are a helpful agent that can use Bing Custom Search tools to assist users. Use the available Bing Custom Search tools to answer questions and perform tasks.""", tools=[tool], ), @@ -97,7 +97,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -111,7 +111,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("Cleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py index 4adb31cec416..386d22dc5e45 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_bing_grounding.py @@ -97,7 +97,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -107,7 +107,7 @@ if annotation.type == "url_citation": print(f"URL Citation: {annotation.url}") elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py index 2c0b98a96a5c..457e79f40388 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_browser_automation.py @@ -61,8 +61,8 @@ agent_name="MyAgent", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are an Agent helping with browser automation tasks. - You can answer questions, provide information, and assist with various tasks + instructions="""You are an Agent helping with browser automation tasks. + You can answer questions, provide information, and assist with various tasks related to web browsing using the Browser Automation tool available to you.""", tools=[tool], ), @@ -88,7 +88,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": item = event.item if item.type == "browser_automation_preview_call": # TODO: support browser_automation_preview_call schema @@ -101,7 +101,7 @@ print(f"Call ID: {getattr(item, 'call_id')}") print(f"Query arguments: {query}") elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py index e2c64efb44be..0f053b52a860 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter.py @@ -71,7 +71,7 @@ # Print code executed by the code interpreter tool. # [START code_output_extraction] code = next((output.code for output in response.output if output.type == "code_interpreter_call"), "") - print(f"Code Interpreter code:") + print("Code Interpreter code:") print(code) # [END code_output_extraction] diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py index 336fc0c17944..68942c42cda8 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_with_files.py @@ -47,7 +47,9 @@ ) # Upload the CSV file for the code interpreter - file = openai_client.files.create(purpose="assistants", file=open(asset_file_path, "rb")) + with open(asset_file_path, "rb") as f: + file = openai_client.files.create(purpose="assistants", file=f) + tool = CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=[file.id])) # [END tool_declaration] diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py index bf448cee686a..d77cc7dae39e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use.py @@ -31,17 +31,17 @@ import os from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, ComputerUsePreviewTool # Import shared helper functions -from computer_use_util import ( +from computer_use_util import ( # pylint: disable=import-error SearchState, load_screenshot_assets, handle_computer_action_and_take_screenshot, print_final_output, ) +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, ComputerUsePreviewTool load_dotenv() @@ -61,7 +61,7 @@ print("Successfully loaded screenshot assets") except FileNotFoundError: print("Failed to load required screenshot assets. Please ensure the asset files exist in ../assets/") - exit(1) + exit(1) # pylint: disable=consider-using-sys-exit # [START tool_declaration] tool = ComputerUsePreviewTool(display_width=1026, display_height=769, environment="windows") @@ -72,8 +72,8 @@ definition=PromptAgentDefinition( model=os.environ.get("COMPUTER_USE_MODEL_DEPLOYMENT_NAME", "computer-use-preview"), instructions=""" - You are a computer automation assistant. - + You are a computer automation assistant. + Be direct and efficient. When you reach the search results page, read and describe the actual search result titles and descriptions you can see. """, tools=[tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py index 2720916e1759..5fd68ef7eda6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_computer_use_async.py @@ -29,18 +29,19 @@ the "Models + endpoints" tab in your Microsoft Foundry project. """ +# pylint: disable=pointless-string-statement import asyncio import os from dotenv import load_dotenv -from azure.identity.aio import DefaultAzureCredential -from azure.ai.projects.aio import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, ComputerUsePreviewTool -from computer_use_util import ( +from computer_use_util import ( # pylint: disable=import-error SearchState, load_screenshot_assets, handle_computer_action_and_take_screenshot, print_final_output, ) +from azure.identity.aio import DefaultAzureCredential +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, ComputerUsePreviewTool load_dotenv() @@ -74,8 +75,8 @@ async def main(): definition=PromptAgentDefinition( model=os.environ.get("COMPUTER_USE_MODEL_DEPLOYMENT_NAME", "computer-use-preview"), instructions=""" - You are a computer automation assistant. - + You are a computer automation assistant. + Be direct and efficient. When you reach the search results page, read and describe the actual search result titles and descriptions you can see. """, tools=[computer_use_tool], diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py index 45a2e29ee4ed..3ceb1ee1d28b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_fabric.py @@ -81,7 +81,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -95,7 +95,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py index c785e1877ade..c604386b5cf7 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py @@ -48,9 +48,10 @@ asset_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../assets/product_info.md")) # Upload file to vector store - file = openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=open(asset_file_path, "rb") - ) + with open(asset_file_path, "rb") as f: + file = openai_client.vector_stores.files.upload_and_poll( + vector_store_id=vector_store.id, file=f + ) print(f"File uploaded to vector store (id: {file.id})") tool = FileSearchTool(vector_store_ids=[vector_store.id]) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py index 682176ad690c..eb17555821be 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py @@ -26,10 +26,9 @@ import os from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential, get_bearer_token_provider +from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool -from openai import OpenAI load_dotenv() @@ -51,9 +50,10 @@ # Upload file to vector store try: - file = openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=open(asset_file_path, "rb") - ) + with open(asset_file_path, "rb") as f: + file = openai_client.vector_stores.files.upload_and_poll( + vector_store_id=vector_store.id, file=f + ) print(f"File uploaded to vector store (id: {file.id})") except FileNotFoundError: print(f"Warning: Asset file not found at {asset_file_path}") @@ -103,7 +103,7 @@ elif event.type == "response.text.done": print(f"\nResponse done with full message: {event.text}") elif event.type == "response.completed": - print(f"\nResponse completed!") + print("\nResponse completed!") print(f"Full response: {event.response.output_text}") print("\n" + "=" * 60) @@ -129,7 +129,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -139,7 +139,7 @@ if annotation.type == "file_citation": print(f"File Citation - Filename: {annotation.filename}, File ID: {annotation.file_id}") elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Agent response: {event.response.output_text}") # Clean up resources @@ -155,7 +155,7 @@ try: openai_client.vector_stores.delete(vector_store.id) print("Vector store deleted") - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught print(f"Warning: Could not delete vector store: {e}") print("\nFile search streaming sample completed!") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py index 8a2fdd22bfc6..0be162b59062 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py @@ -36,7 +36,7 @@ endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] -async def main() -> None: +async def main() -> None: # pylint: disable=too-many-statements async with ( DefaultAzureCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, @@ -51,9 +51,10 @@ async def main() -> None: # Upload file to vector store try: - file = await openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=open(asset_file_path, "rb") - ) + with open(asset_file_path, "rb") as f: + file = await openai_client.vector_stores.files.upload_and_poll( + vector_store_id=vector_store.id, file=f + ) print(f"File uploaded to vector store (id: {file.id})") except FileNotFoundError: print(f"Warning: Asset file not found at {asset_file_path}") @@ -104,7 +105,7 @@ async def main() -> None: elif event.type == "response.text.done": print(f"\nResponse done with full message: {event.text}") elif event.type == "response.completed": - print(f"\nResponse completed!") + print("\nResponse completed!") print(f"Full response: {event.response.output_text}") print("\n" + "=" * 60) @@ -134,7 +135,7 @@ async def main() -> None: elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -144,7 +145,7 @@ async def main() -> None: if annotation.type == "file_citation": print(f"File Citation - Filename: {annotation.filename}, File ID: {annotation.file_id}") elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Agent response: {event.response.output_text}") # Clean up resources @@ -160,7 +161,7 @@ async def main() -> None: try: await openai_client.vector_stores.delete(vector_store.id) print("Vector store deleted") - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught print(f"Warning: Could not delete vector store: {e}") print("\nFile search streaming sample completed!") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py index 33166f2ad06f..7e0ac8e0e56e 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool.py @@ -23,13 +23,14 @@ the "Models + endpoints" tab in your Microsoft Foundry project. """ +# pylint: disable=docstring-missing-param,docstring-missing-return,docstring-missing-rtype import os import json from dotenv import load_dotenv -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, Tool, FunctionTool -from azure.identity import DefaultAzureCredential from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, FunctionTool load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py index 1ecd17dee095..249f9a936419 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_function_tool_async.py @@ -24,14 +24,15 @@ the "Models + endpoints" tab in your Microsoft Foundry project. """ +# pylint: disable=docstring-missing-param,docstring-missing-return,docstring-missing-rtype import os import json import asyncio from dotenv import load_dotenv -from azure.ai.projects.aio import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, Tool, FunctionTool -from azure.identity.aio import DefaultAzureCredential from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam +from azure.identity.aio import DefaultAzureCredential +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, FunctionTool load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py index 9b86f113f234..fb0a553328e9 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp.py @@ -25,10 +25,10 @@ import os from dotenv import load_dotenv +from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool -from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, MCPTool load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py index 917caae9da33..55c32cd0602c 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_async.py @@ -26,10 +26,10 @@ import os import asyncio from dotenv import load_dotenv +from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool -from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py index d34313d8651d..091db4707b59 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection.py @@ -27,10 +27,10 @@ import os from dotenv import load_dotenv +from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool -from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, MCPTool load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py index 974699e42901..8de0155f94ec 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_mcp_with_project_connection_async.py @@ -28,10 +28,10 @@ import os import asyncio from dotenv import load_dotenv +from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool -from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py index 5e45e9ec41bf..804c330cd0ce 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi.py @@ -24,9 +24,9 @@ """ import os +from typing import Any, cast import jsonref from dotenv import load_dotenv -from typing import Any, cast from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( @@ -49,7 +49,7 @@ weather_asset_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../assets/weather_openapi.json")) # [START tool_declaration] - with open(weather_asset_file_path, "r") as f: + with open(weather_asset_file_path, "r", encoding="utf-8") as f: openapi_weather = cast(dict[str, Any], jsonref.loads(f.read())) tool = OpenApiTool( diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py index b580ee51eb2a..6c7d88ba4ba4 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_openapi_with_project_connection.py @@ -27,9 +27,9 @@ """ import os +from typing import Any, cast import jsonref from dotenv import load_dotenv -from typing import Any, cast from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py index 6af21a566a6c..394e486cc36a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_sharepoint.py @@ -60,7 +60,7 @@ agent_name="MyAgent", definition=PromptAgentDefinition( model=os.environ["FOUNDRY_MODEL_NAME"], - instructions="""You are a helpful agent that can use SharePoint tools to assist users. + instructions="""You are a helpful agent that can use SharePoint tools to assist users. Use the available SharePoint tools to answer questions and perform tasks.""", tools=[tool], ), @@ -85,7 +85,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -99,7 +99,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Agent response: {event.response.output_text}") print("Cleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py index 97ae4365f075..3fe3835b093a 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_to_agent.py @@ -85,7 +85,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": item = event.item if item.type == "a2a_preview_call": @@ -97,7 +97,7 @@ elif item.type == "a2a_preview_call_output": print(f"Response ID: {getattr(item, 'id')}") elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py index 5eea41125bf8..3d21a92ae1b6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search.py @@ -87,7 +87,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -101,7 +101,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py index 4148bb7fd29e..cf68ceaf09cf 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_preview.py @@ -83,7 +83,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -97,7 +97,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py index b9da3e2e856b..e4ad578919b5 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_web_search_with_custom_search.py @@ -99,7 +99,7 @@ elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"\nFollow-up response done!") + print("\nFollow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -113,7 +113,7 @@ f"End index: {annotation.end_index}" ) elif event.type == "response.completed": - print(f"\nFollow-up completed!") + print("\nFollow-up completed!") print(f"Full response: {event.response.output_text}") print("\nCleaning up...") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py index e3c2f9291a09..693d948c8e6d 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_coherence.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv -import json import os import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -80,7 +80,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -114,7 +114,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -126,7 +126,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py index 5ab4e46690ac..0dc7f400fb6c 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_fluency.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -80,7 +80,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -105,7 +105,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -117,7 +117,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py index 68ef5ef7bbc3..91a0b4251b98 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/agent_utils.py @@ -3,19 +3,20 @@ # Licensed under the MIT License. # ------------------------------------ -from dotenv import load_dotenv import os import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -54,7 +55,7 @@ def run_evaluator( data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -71,7 +72,7 @@ def run_evaluator( ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -83,7 +84,7 @@ def run_evaluator( while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py index b5be2881ae38..1b2afc8d6705 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_generic_agentic_evaluator/sample_generic_agentic_evaluator.py @@ -23,10 +23,11 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -from agent_utils import run_evaluator -from schema_mappings import evaluator_to_data_source_config, evaluator_to_data_mapping + +from dotenv import load_dotenv +from agent_utils import run_evaluator # pylint: disable=import-error +from schema_mappings import evaluator_to_data_source_config, evaluator_to_data_mapping # pylint: disable=import-error from openai.types.evals.create_eval_jsonl_run_data_source_param import SourceFileContentContent load_dotenv() @@ -35,9 +36,8 @@ def _get_evaluator_initialization_parameters(evaluator_name: str) -> dict[str, str]: if evaluator_name == "task_navigation_efficiency": return {"matching_mode": "exact_match"} # Can be "exact_match", "in_order_match", or "any_order_match" - else: - model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini - return {"deployment_name": model_deployment_name} + model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") # Sample : gpt-4o-mini + return {"deployment_name": model_deployment_name} def _get_evaluation_contents() -> list[SourceFileContentContent]: diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py index 8aa48ca726a6..958316d45676 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_groundedness.py @@ -23,25 +23,25 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() -def main() -> None: +def main() -> None: # pylint: disable=too-many-locals endpoint = os.environ[ "FOUNDRY_PROJECT_ENDPOINT" ] # Sample : https://.services.ai.azure.com/api/projects/ @@ -96,7 +96,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -252,7 +252,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -264,7 +264,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py index 4601d4587925..cda8cdb30c74 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_intent_resolution.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -91,7 +91,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -238,7 +238,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -250,7 +250,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py index 553770301b91..883e5e56f71b 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_relevance.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -84,7 +84,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -145,7 +145,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -157,7 +157,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py index c37eafae3d41..a14dc3a67e44 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_response_completeness.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -82,7 +82,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -124,7 +124,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -136,7 +136,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py index 30066c7ff66d..d46398d61677 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_adherence.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -92,7 +92,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -210,7 +210,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -222,7 +222,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py index 73dce3ae4070..68ca90bd6bbf 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_completion.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -92,7 +92,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -248,7 +248,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -260,7 +260,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py index 47dd01c5eeb0..d3d7862bc18e 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_task_navigation_efficiency.py @@ -22,20 +22,20 @@ Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -83,7 +83,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -171,7 +171,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -183,7 +183,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py index d3aea2348416..f3620e56f6cc 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_accuracy.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -94,7 +94,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -293,7 +293,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -305,7 +305,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py index 12e1746db771..7320c28e7e6f 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_call_success.py @@ -23,21 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.eval_create_params import DataSourceConfigCustom from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) -from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -88,7 +87,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -229,7 +228,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -241,7 +240,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py index b08119e4f88c..0fc2cecb338b 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_input_accuracy.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -92,7 +92,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -306,7 +306,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -318,7 +318,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py index 72ea0e1f260c..818567f7ad16 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_output_utilization.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -90,7 +90,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -247,7 +247,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -259,7 +259,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py index 38f49bc9c582..97452fe11881 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/agentic_evaluators/sample_tool_selection.py @@ -23,20 +23,20 @@ 2) FOUNDRY_MODEL_NAME - Required. The name of the model deployment to use for evaluation. """ -from dotenv import load_dotenv import os -import json import time from pprint import pprint -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient +from dotenv import load_dotenv + from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -94,7 +94,7 @@ def main() -> None: data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -212,7 +212,7 @@ def main() -> None: ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -224,7 +224,7 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Status: {run.status}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py index bc54ebd5c70d..7c4296fe0cc8 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_evaluation.py @@ -31,12 +31,12 @@ from typing import Union from pprint import pprint from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition from openai.types.eval_create_params import DataSourceConfigCustom from openai.types.evals.run_create_response import RunCreateResponse from openai.types.evals.run_retrieve_response import RunRetrieveResponse +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition load_dotenv() endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py index 73b32e3c8f26..e3e5fd3feb22 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation.py @@ -31,11 +31,11 @@ from typing import Union from pprint import pprint from dotenv import load_dotenv +from openai.types.evals.run_create_response import RunCreateResponse +from openai.types.evals.run_retrieve_response import RunRetrieveResponse from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition -from openai.types.evals.run_create_response import RunCreateResponse -from openai.types.evals.run_retrieve_response import RunRetrieveResponse load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py index 45de7b5d2e6f..6aedee95b8ab 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_agent_response_evaluation_with_function_tool.py @@ -25,18 +25,19 @@ the "Models + endpoints" tab in your Microsoft Foundry project. """ +# pylint: disable=docstring-missing-param,docstring-missing-return,docstring-missing-rtype import json import os import time from typing import Union from pprint import pprint from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import PromptAgentDefinition, Tool, FunctionTool from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam from openai.types.evals.run_create_response import RunCreateResponse from openai.types.evals.run_retrieve_response import RunRetrieveResponse +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, Tool, FunctionTool load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py index 8c8ac624e790..484bf3260445 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_continuous_evaluation_rule.py @@ -118,7 +118,7 @@ conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": f"Question {i}: What is the capital city?"}], ) - print(f"Added a user message to the conversation") + print("Added a user message to the conversation") response = openai_client.responses.create( conversation=conversation.id, @@ -145,7 +145,7 @@ MAX_LOOP = 20 for _ in range(0, MAX_LOOP): - print(f"Waiting for eval run to complete...") + print("Waiting for eval run to complete...") eval_run_list = openai_client.evals.runs.list( eval_id=eval_object.id, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py index c1d788d294cb..6eed731eb0e6 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog.py @@ -23,6 +23,8 @@ """ import os +from pprint import pprint +from dotenv import load_dotenv from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( @@ -36,9 +38,6 @@ EvaluatorMetricType, ) -from pprint import pprint -from dotenv import load_dotenv - load_dotenv() endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] @@ -57,19 +56,19 @@ definition=PromptBasedEvaluatorDefinition( prompt_text="""You are an evaluator. Rate the GROUNDEDNESS (factual correctness without unsupported claims) of the system response to the customer query. - + Scoring (1–5): 1 = Mostly fabricated/incorrect 2 = Many unsupported claims 3 = Mixed: some facts but notable errors/guesses 4 = Mostly factual; minor issues 5 = Fully factual; no unsupported claims - + Return ONLY a single integer 1–5 as score in valid json response e.g {\"score\": int}. - + Query: {query} - + Response: {response} """, diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py index e00037924f8c..dffe0f18ed76 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_code_based_evaluators.py @@ -24,21 +24,19 @@ """ import os -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import EvaluatorCategory, EvaluatorDefinitionType +import time +from pprint import pprint +from dotenv import load_dotenv from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom - -import time -from pprint import pprint - -from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import EvaluatorCategory, EvaluatorDefinitionType load_dotenv() @@ -189,7 +187,7 @@ while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Evaluation run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py index e17293f21492..f89cd061d701 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_catalog_prompt_based_evaluators.py @@ -57,21 +57,19 @@ """ import os -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import EvaluatorCategory, EvaluatorDefinitionType +import time +from pprint import pprint +from dotenv import load_dotenv from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom - -from pprint import pprint -import time - -from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import EvaluatorCategory, EvaluatorDefinitionType load_dotenv() @@ -97,8 +95,8 @@ "prompt_text": """ You are a Groundedness Evaluator. - Your task is to evaluate how well the given response is grounded in the provided ground truth. - Groundedness means the response’s statements are factually supported by the ground truth. + Your task is to evaluate how well the given response is grounded in the provided ground truth. + Groundedness means the response’s statements are factually supported by the ground truth. Evaluate factual alignment only — ignore grammar, fluency, or completeness. --- @@ -116,10 +114,10 @@ --- ### Scoring Scale (1–5): - 5 → Fully grounded. All claims supported by ground truth. - 4 → Mostly grounded. Minor unsupported details. - 3 → Partially grounded. About half the claims supported. - 2 → Mostly ungrounded. Only a few details supported. + 5 → Fully grounded. All claims supported by ground truth. + 4 → Mostly grounded. Minor unsupported details. + 3 → Partially grounded. About half the claims supported. + 2 → Mostly ungrounded. Only a few details supported. 1 → Not grounded. Almost all information unsupported. --- @@ -255,7 +253,7 @@ while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py index 256bacef18b4..a125dd62ed04 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_cluster_insight.py @@ -32,14 +32,6 @@ from typing import Union from pprint import pprint from dotenv import load_dotenv -from azure.ai.projects.models import ( - OperationState, - EvaluationRunClusterInsightRequest, - Insight, - InsightModelConfiguration, -) -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient from openai.types.eval_create_params import DataSourceConfigCustom, TestingCriterionLabelModel from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, @@ -48,6 +40,14 @@ ) from openai.types.evals.run_create_response import RunCreateResponse from openai.types.evals.run_retrieve_response import RunRetrieveResponse +from azure.ai.projects.models import ( + OperationState, + EvaluationRunClusterInsightRequest, + Insight, + InsightModelConfiguration, +) +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -135,7 +135,7 @@ print(f"Started insight generation (id: {clusterInsight.insight_id})") while clusterInsight.state not in [OperationState.SUCCEEDED, OperationState.FAILED]: - print(f"Waiting for insight to be generated...") + print("Waiting for insight to be generated...") clusterInsight = project_client.beta.insights.get(insight_id=clusterInsight.insight_id) print(f"Insight status: {clusterInsight.state}") time.sleep(5) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py index 1cf98af0efce..0b48752f4a90 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluation_compare_insight.py @@ -31,6 +31,12 @@ import time from pprint import pprint from dotenv import load_dotenv +from openai.types.eval_create_params import DataSourceConfigCustom, TestingCriterionLabelModel +from openai.types.evals.create_eval_jsonl_run_data_source_param import ( + CreateEvalJSONLRunDataSourceParam, + SourceFileContent, +) +from openai.types.evals.run_retrieve_response import RunRetrieveResponse from azure.ai.projects.models import ( OperationState, EvaluationComparisonInsightRequest, @@ -38,12 +44,6 @@ ) from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from openai.types.eval_create_params import DataSourceConfigCustom, TestingCriterionLabelModel -from openai.types.evals.create_eval_jsonl_run_data_source_param import ( - CreateEvalJSONLRunDataSourceParam, - SourceFileContent, -) -from openai.types.evals.run_retrieve_response import RunRetrieveResponse load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py index c76c08bf6191..2fc9c4b7ac73 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_ai_assisted.py @@ -24,18 +24,17 @@ """ import os - -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient import time from pprint import pprint +from dotenv import load_dotenv from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom -from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -164,7 +163,7 @@ ), ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Evaluation Run by Id") @@ -174,7 +173,7 @@ while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py index 6259ad3aac5c..8bb044514edd 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_csv.py @@ -30,18 +30,18 @@ """ import os - -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient - import time +from datetime import datetime, timezone from pprint import pprint + +from dotenv import load_dotenv from openai.types.eval_create_params import DataSourceConfigCustom + +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( DatasetVersion, ) -from dotenv import load_dotenv -from datetime import datetime, timezone load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py index 29559c76572a..1880f48fa5b3 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_dataset_id.py @@ -27,19 +27,17 @@ """ import os - -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient - import time +from datetime import datetime from pprint import pprint +from dotenv import load_dotenv from openai.types.evals.create_eval_jsonl_run_data_source_param import CreateEvalJSONLRunDataSourceParam, SourceFileID from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient from azure.ai.projects.models import ( DatasetVersion, ) -from dotenv import load_dotenv -from datetime import datetime load_dotenv() @@ -135,7 +133,7 @@ while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py index f18080047b25..0c0adf21de89 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data.py @@ -24,18 +24,17 @@ """ import os - -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient import time from pprint import pprint +from dotenv import load_dotenv from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom -from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -96,7 +95,7 @@ data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -150,7 +149,7 @@ ), ) - print(f"Eval Run created") + print("Eval Run created") pprint(eval_run_object) print("Get Eval Run by Id") @@ -160,7 +159,7 @@ while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py index 8c8100efbca3..ce3168f25c58 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_inline_data_oai.py @@ -24,10 +24,9 @@ """ import os - -from azure.identity import DefaultAzureCredential import time from pprint import pprint +from dotenv import load_dotenv from openai import OpenAI from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, @@ -35,8 +34,7 @@ SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom -from dotenv import load_dotenv -from azure.identity import get_bearer_token_provider +from azure.identity import DefaultAzureCredential, get_bearer_token_provider load_dotenv() @@ -87,7 +85,7 @@ data_source_config=data_source_config, testing_criteria=testing_criteria, # type: ignore ) -print(f"Evaluation created") +print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -141,12 +139,12 @@ ), ) -print(f"Eval Run created") +print("Eval Run created") pprint(eval_run_object) while True: run = client.evals.runs.retrieve(run_id=eval_run_object.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): print("Get Eval Run by Id") output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py index 57f839a022ee..03d7ee54c949 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_builtin_with_traces.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long,useless-suppression +# pylint: disable=line-too-long,useless-suppression,docstring-missing-param,docstring-missing-return,docstring-missing-rtype,unused-argument # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -32,15 +32,13 @@ import os import time from datetime import datetime, timedelta, timezone +from pprint import pprint from typing import Any, Dict, List - from dotenv import load_dotenv from azure.identity import DefaultAzureCredential from azure.monitor.query import LogsQueryClient, LogsQueryStatus from azure.ai.projects import AIProjectClient -from pprint import pprint - load_dotenv() @@ -85,7 +83,7 @@ def get_trace_ids( Returns: List of distinct operation IDs (trace IDs). """ - query = f""" + query = """ dependencies | where timestamp between (datetime({start_time.isoformat()}) .. datetime({end_time.isoformat()})) | extend agent_id = tostring(customDimensions["gen_ai.agent.id"]) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py index 531924ed51bc..f04644142485 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_graders.py @@ -25,17 +25,18 @@ import os -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient import time from pprint import pprint + +from dotenv import load_dotenv from openai.types.evals.create_eval_jsonl_run_data_source_param import ( CreateEvalJSONLRunDataSourceParam, SourceFileContent, SourceFileContentContent, ) from openai.types.eval_create_params import DataSourceConfigCustom -from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() @@ -180,7 +181,7 @@ while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py index 8273afaa747e..290e2b019a44 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_evaluations_score_model_grader_with_image.py @@ -24,13 +24,12 @@ import os import base64 -from PIL import Image -from io import BytesIO - -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient import time +from io import BytesIO from pprint import pprint +from PIL import Image + +from dotenv import load_dotenv from openai.types.evals.create_eval_completions_run_data_source_param import ( CreateEvalCompletionsRunDataSourceParam, SourceFileContent, @@ -41,7 +40,8 @@ ) from openai.types.responses import EasyInputMessageParam from openai.types.eval_create_params import DataSourceConfigCustom -from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() file_path = os.path.abspath(__file__) @@ -51,7 +51,7 @@ model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME", "") -def image_to_data_uri(image_path: str) -> str: +def image_to_data_uri(image_path: str) -> str: # pylint: disable=redefined-outer-name with Image.open(image_path) as img: buffered = BytesIO() img.save(buffered, format=img.format or "PNG") @@ -182,7 +182,7 @@ def image_to_data_uri(image_path: str) -> str: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) pprint(output_items) print(f"Eval Run Report URL: {run.report_url}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py index 53066a0fce99..bd169235a91f 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_model_evaluation.py @@ -31,11 +31,11 @@ from pprint import pprint from typing import Union from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient from openai.types.eval_create_params import DataSourceConfigCustom from openai.types.evals.run_create_response import RunCreateResponse from openai.types.evals.run_retrieve_response import RunRetrieveResponse +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py index b9ff12eba463..ea8628cbd8d1 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_redteam_evaluations.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long,useless-suppression +# pylint: disable=line-too-long,useless-suppression,wrong-import-order # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -38,11 +38,10 @@ ) import json import time -from azure.ai.projects.models import EvaluationTaxonomy from typing import Union -def main() -> None: +def main() -> None: # pylint: disable=too-many-statements load_dotenv() # endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT", "") @@ -67,7 +66,7 @@ def main() -> None: data_source_config = {"type": "azure_ai_source", "scenario": "red_team"} testing_criteria = _get_agent_safety_evaluation_criteria() - print(f"Defining testing criteria for red teaming for agent target") + print("Defining testing criteria for red teaming for agent target") pprint(testing_criteria) print("Creating red teaming evaluation") @@ -95,7 +94,7 @@ def main() -> None: taxonomy = project_client.beta.evaluation_taxonomies.create(name=agent_name, body=eval_taxonomy_input) taxonomy_path = os.path.join(tempfile.gettempdir(), f"taxonomy_{agent_name}.json") - with open(taxonomy_path, "w") as f: + with open(taxonomy_path, "w", encoding="utf-8") as f: f.write(json.dumps(_to_json_primitive(taxonomy), indent=2)) print(f"Red teaming Taxonomy created for agent: {agent_name}. Taxonomy written to {taxonomy_path}") @@ -125,10 +124,10 @@ def main() -> None: while True: run = client.evals.runs.retrieve(run_id=eval_run_response.id, eval_id=eval_object.id) - if run.status == "completed" or run.status == "failed": + if run.status in ("completed", "failed"): output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) output_items_path = os.path.join(tempfile.gettempdir(), f"redteam_eval_output_items_{agent_name}.json") - with open(output_items_path, "w") as f: + with open(output_items_path, "w", encoding="utf-8") as f: f.write(json.dumps(_to_json_primitive(output_items), indent=2)) print( f"RedTeam Eval Run completed with status: {run.status}. Output items written to {output_items_path}" @@ -223,7 +222,7 @@ def _to_json_primitive(obj): if hasattr(obj, method): try: return _to_json_primitive(getattr(obj, method)()) - except Exception: + except Exception: # pylint: disable=broad-exception-caught pass if hasattr(obj, "__dict__"): return _to_json_primitive({k: v for k, v in vars(obj).items() if not k.startswith("_")}) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py index 742c6e100070..c24651ac8b0b 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_scheduled_evaluations.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long,useless-suppression +# pylint: disable=line-too-long,useless-suppression,wrong-import-order,ungrouped-imports,no-else-raise,raise-missing-from # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -57,7 +57,6 @@ ) import json import time -from azure.ai.projects.models import EvaluationTaxonomy def main() -> None: @@ -69,7 +68,7 @@ def main() -> None: schedule_redteam_evaluation() -def assign_rbac(): +def assign_rbac(): # pylint: disable=too-many-statements """ Assign the "Azure AI User" role to the Microsoft Foundry project's Managed Identity. """ @@ -97,7 +96,7 @@ def assign_rbac(): return account_name = match.group(1) project_name = match.group(2) - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught print(f"Error parsing endpoint: {e}") return @@ -135,7 +134,7 @@ def assign_rbac(): print("Error: Project does not have a managed identity enabled") return - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught print(f"Error retrieving project resource: {e}") return @@ -149,7 +148,7 @@ def assign_rbac(): # Create role assignment role_assignment_name = str(uuid.uuid4()) - print(f"Assigning 'Azure AI User' role to managed identity...") + print("Assigning 'Azure AI User' role to managed identity...") role_assignment = auth_client.role_assignments.create( scope=scope, @@ -161,10 +160,10 @@ def assign_rbac(): }, ) - print(f"Successfully assigned 'Azure AI User' role to project managed identity") + print("Successfully assigned 'Azure AI User' role to project managed identity") print(f"Role assignment ID: {role_assignment.name}") - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught print(f"Error during role assignment: {e}") # Check for specific error types and provide helpful guidance @@ -208,7 +207,7 @@ def assign_rbac(): print("This usually indicates a service availability issue.") else: - print(f"\n❌ UNEXPECTED ERROR:") + print("\n❌ UNEXPECTED ERROR:") print("An unexpected error occurred. Please check the error details above.") raise @@ -275,7 +274,7 @@ def schedule_dataset_evaluation() -> None: data_source_config=data_source_config, # type: ignore testing_criteria=testing_criteria, # type: ignore ) - print(f"Evaluation created") + print("Evaluation created") print("Get Evaluation by Id") eval_object_response = client.evals.retrieve(eval_object.id) @@ -292,7 +291,7 @@ def schedule_dataset_evaluation() -> None: ), } - print(f"Eval Run:") + print("Eval Run:") pprint(eval_run_object) print("Creating Schedule for dataset evaluation") schedule = Schedule( @@ -324,7 +323,7 @@ def schedule_dataset_evaluation() -> None: print("Dataset deleted") -def schedule_redteam_evaluation() -> None: +def schedule_redteam_evaluation() -> None: # pylint: disable=too-many-locals load_dotenv() # endpoint = os.environ.get("FOUNDRY_PROJECT_ENDPOINT", "") @@ -354,7 +353,7 @@ def schedule_redteam_evaluation() -> None: data_source_config = {"type": "azure_ai_source", "scenario": "red_team"} testing_criteria = _get_agent_safety_evaluation_criteria() - print(f"Defining testing criteria for red teaming for agent target") + print("Defining testing criteria for red teaming for agent target") pprint(testing_criteria) print("Creating Evaluation") @@ -384,7 +383,7 @@ def schedule_redteam_evaluation() -> None: taxonomy_path = os.path.join(data_folder, f"taxonomy_{agent_name}.json") # Create the data folder if it doesn't exist os.makedirs(data_folder, exist_ok=True) - with open(taxonomy_path, "w") as f: + with open(taxonomy_path, "w", encoding="utf-8") as f: f.write(json.dumps(_to_json_primitive(taxonomy), indent=2)) print(f"RedTeaming Taxonomy created for agent: {agent_name}. Taxonomy written to {taxonomy_path}") eval_run_object = { @@ -510,7 +509,7 @@ def _to_json_primitive(obj): if hasattr(obj, method): try: return _to_json_primitive(getattr(obj, method)()) - except Exception: + except Exception: # pylint: disable=broad-exception-caught pass if hasattr(obj, "__dict__"): return _to_json_primitive({k: v for k, v in vars(obj).items() if not k.startswith("_")}) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py index c67840c6325d..0e01e95fd8d8 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_agent_evaluation.py @@ -38,12 +38,14 @@ import time from pprint import pprint from typing import Union + from dotenv import load_dotenv +from openai.types.evals.run_create_response import RunCreateResponse +from openai.types.evals.run_retrieve_response import RunRetrieveResponse + from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.ai.projects.models import PromptAgentDefinition -from openai.types.evals.run_create_response import RunCreateResponse -from openai.types.evals.run_retrieve_response import RunRetrieveResponse load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py index 7bf1cc1a2e77..bf0a0120299b 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py @@ -37,12 +37,14 @@ import time from pprint import pprint from typing import Union + from dotenv import load_dotenv -from azure.identity import DefaultAzureCredential -from azure.ai.projects import AIProjectClient from openai.types.evals.run_create_response import RunCreateResponse from openai.types.evals.run_retrieve_response import RunRetrieveResponse +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient + load_dotenv() endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files.py b/sdk/ai/azure-ai-projects/samples/files/sample_files.py index 51dac448a43e..8c96a934f300 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files.py @@ -22,10 +22,11 @@ """ import os -from azure.identity import DefaultAzureCredential +from pathlib import Path + from dotenv import load_dotenv +from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from pathlib import Path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py index 953fa2021c1b..d3bbe42b4896 100644 --- a/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py +++ b/sdk/ai/azure-ai-projects/samples/files/sample_files_async.py @@ -23,10 +23,11 @@ import asyncio import os +from pathlib import Path + from dotenv import load_dotenv from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient -from pathlib import Path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py index 2a31c816741e..7366b24b49b1 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job.py @@ -26,9 +26,9 @@ import os from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py index a21e9ca10387..8b04fc71b0ae 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_dpo_job_async.py @@ -27,9 +27,9 @@ import asyncio import os from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py index f3eebc756dfc..d944f157314c 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job.py @@ -27,9 +27,9 @@ import os from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py index 676a4a030d11..ca4e3e3789ac 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_oss_models_supervised_job_async.py @@ -28,9 +28,9 @@ import os import asyncio from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py index 9a222cffa076..f49c2dc474fd 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job.py @@ -27,9 +27,9 @@ import os from typing import Any, Dict from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py index 3d759737fbab..62ab5fed22c2 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_reinforcement_job_async.py @@ -27,9 +27,9 @@ import asyncio import os from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py index a871254e90dd..2da39d05354e 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long,useless-suppression +# pylint: disable=line-too-long,useless-suppression,docstring-missing-param,docstring-missing-return,docstring-missing-rtype # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -32,13 +32,12 @@ """ import os -import time from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient from azure.mgmt.cognitiveservices.models import Deployment, DeploymentProperties, DeploymentModel, Sku -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py index 94e5d2c94603..42c93f8b0ebf 100644 --- a/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py +++ b/sdk/ai/azure-ai-projects/samples/finetuning/sample_finetuning_supervised_job_async.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long,useless-suppression +# pylint: disable=line-too-long,useless-suppression,docstring-missing-param,docstring-missing-return,docstring-missing-rtype # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -34,11 +34,11 @@ import asyncio import os from dotenv import load_dotenv +from fine_tuning_sample_helper import resolve_data_file_path # pylint: disable=import-error from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient from azure.mgmt.cognitiveservices.aio import CognitiveServicesManagementClient as CognitiveServicesManagementClientAsync from azure.mgmt.cognitiveservices.models import Deployment, DeploymentProperties, DeploymentModel, Sku -from fine_tuning_sample_helper import resolve_data_file_path load_dotenv() @@ -104,7 +104,7 @@ async def deploy_model(openai_client, credential, job_id): deployment=deployment_config, ) - print(f"Waiting for deployment to complete...") + print("Waiting for deployment to complete...") await deployment.result() print(f"Model deployment completed: {deployment_name}") diff --git a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py index f84e6358d7b0..278f0f2c867e 100644 --- a/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py +++ b/sdk/ai/azure-ai-projects/samples/mcp_client/sample_mcp_tool_async.py @@ -1,4 +1,4 @@ -# pylint: disable=line-too-long,useless-suppression +# pylint: disable=line-too-long,useless-suppression,used-before-assignment,consider-using-with # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -48,11 +48,11 @@ import os import logging from dotenv import load_dotenv -from azure.ai.projects.aio import AIProjectClient -from azure.identity.aio import DefaultAzureCredential from mcp import ClientSession from mcp.types import ImageContent from mcp.client.streamable_http import streamablehttp_client +from azure.ai.projects.aio import AIProjectClient +from azure.identity.aio import DefaultAzureCredential load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py index dc0e03d66528..e6c1b34b64b5 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced.py @@ -34,6 +34,7 @@ import os from dotenv import load_dotenv +from openai.types.responses import EasyInputMessageParam from azure.core.exceptions import ResourceNotFoundError from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient @@ -42,7 +43,6 @@ MemoryStoreDefaultOptions, MemorySearchOptions, ) -from openai.types.responses import EasyInputMessageParam load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py index 4192fe11ee57..dc6950617e6a 100644 --- a/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py +++ b/sdk/ai/azure-ai-projects/samples/memories/sample_memory_advanced_async.py @@ -35,6 +35,7 @@ import asyncio import os from dotenv import load_dotenv +from openai.types.responses import EasyInputMessageParam from azure.core.exceptions import ResourceNotFoundError from azure.identity.aio import DefaultAzureCredential from azure.ai.projects.aio import AIProjectClient @@ -43,7 +44,6 @@ MemoryStoreDefaultOptions, MemorySearchOptions, ) -from openai.types.responses import EasyInputMessageParam load_dotenv() diff --git a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py index 016cc1d532f2..cd7969dc6b9c 100644 --- a/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py +++ b/sdk/ai/azure-ai-projects/samples/red_team/sample_red_team_async.py @@ -52,7 +52,6 @@ async def sample_red_team_async() -> None: async with ( DefaultAzureCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, - project_client.get_openai_client() as openai_client, ): # [START red_team_sample] print("Creating a Red Team scan for direct model testing") diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py index 065046543a61..f07f5fb6533e 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_events.py @@ -56,6 +56,6 @@ elif event.type == "response.output_text.delta": print(event.delta, end="", flush=True) elif event.type == "response.text.done": - print(f"\n\nResponse text done. Access final text in 'event.text'") + print("\n\nResponse text done. Access final text in 'event.text'") elif event.type == "response.completed": - print(f"\n\nResponse completed. Access final text in 'event.response.output_text'") + print("\n\nResponse completed. Access final text in 'event.response.output_text'") diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py index 87db2ada4d88..87a79415ca13 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_stream_manager.py @@ -54,6 +54,6 @@ elif event.type == "response.output_text.delta": print(event.delta, end="", flush=True) elif event.type == "response.text.done": - print(f"\n\nResponse text done. Access final text in 'event.text'") + print("\n\nResponse text done. Access final text in 'event.text'") elif event.type == "response.completed": - print(f"\n\nResponse completed. Access final text in 'event.response.output_text'") + print("\n\nResponse completed. Access final text in 'event.response.output_text'") diff --git a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py index 1119d969df9c..3f08898a32f6 100644 --- a/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py +++ b/sdk/ai/azure-ai-projects/samples/responses/sample_responses_structured_output.py @@ -28,9 +28,9 @@ import os from dotenv import load_dotenv +from pydantic import BaseModel, Field from azure.identity import DefaultAzureCredential from azure.ai.projects import AIProjectClient -from pydantic import BaseModel, Field load_dotenv() diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/gen_ai_trace_verifier.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/gen_ai_trace_verifier.py index 5ad356d2470e..1fa804b06600 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/gen_ai_trace_verifier.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/gen_ai_trace_verifier.py @@ -60,7 +60,7 @@ def check_span_attributes(self, span, attributes): ) if span.attributes[attribute_name] < 0: raise AssertionError("Attribute value " + str(span.attributes[attribute_name]) + " is negative") - elif attribute_value != "" and span.attributes[attribute_name] != attribute_value: + elif attribute_value not in ("", span.attributes[attribute_name]): raise AssertionError( "Attribute value " + str(span.attributes[attribute_name]) @@ -109,7 +109,7 @@ def check_decorator_span_attributes(self, span: Span, attributes: List[tuple]) - raise AssertionError("Attribute value " + str(span_value) + " is not a number") if span_value < 0: raise AssertionError("Attribute value " + str(span_value) + " is negative") - elif attribute_value != "" and span_value != attribute_value: + elif attribute_value not in ("", span_value): raise AssertionError( "Attribute value " + str(span_value) + " does not match with " + str(attribute_value) ) @@ -122,9 +122,9 @@ def check_decorator_span_attributes(self, span: Span, attributes: List[tuple]) - def is_valid_json(self, my_string): try: json.loads(my_string) - except ValueError as e1: + except ValueError: return False - except TypeError as e2: + except TypeError: return False return True @@ -136,12 +136,11 @@ def check_json_string(self, expected_json, actual_json): # Handle both dict and list (array) formats if isinstance(expected_obj, list) and isinstance(actual_obj, list): return self.check_event_lists(expected_obj, actual_obj) - elif isinstance(expected_obj, dict) and isinstance(actual_obj, dict): + if isinstance(expected_obj, dict) and isinstance(actual_obj, dict): return self.check_event_attributes(expected_obj, actual_obj) - else: - raise AssertionError( - f"check_json_string: type mismatch - expected {type(expected_obj).__name__}, got {type(actual_obj).__name__}" - ) + raise AssertionError( + f"check_json_string: type mismatch - expected {type(expected_obj).__name__}, got {type(actual_obj).__name__}" + ) def check_event_lists(self, expected_list, actual_list): """Check if two lists match, handling nested dicts/lists.""" diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/memory_trace_exporter.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/memory_trace_exporter.py index e3d1345c6e73..19b650c9fb28 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/memory_trace_exporter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/memory_trace_exporter.py @@ -2,9 +2,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ +from typing import List, Sequence from opentelemetry.sdk.trace import Span from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult -from typing import List, Sequence class MemoryTraceExporter(SpanExporter): @@ -46,4 +46,4 @@ def get_spans_by_name(self, name: str) -> List[Span]: return [span for span in self._trace_list if span.name == name] def get_spans(self) -> List[Span]: - return [span for span in self._trace_list] + return list(self._trace_list) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py index bdd35586e4e0..ac2a033ec164 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py @@ -3,54 +3,34 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -# cSpell:disable# cSpell:disable -import pytest +# cSpell:disable import os from typing import Optional -from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from azure.ai.projects.models import PromptAgentDefinition, PromptAgentDefinitionTextOptions - -from azure.ai.projects.models import ( - Reasoning, - FunctionTool, - # ResponseTextFormatConfigurationText, -) +import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error from devtools_testutils import ( recorded_by_proxy, ) - from test_base import servicePreparer -from test_ai_instrumentor_base import ( +from test_ai_instrumentor_base import ( # pylint: disable=import-error TestAiAgentsInstrumentorBase, - MessageCreationMode, CONTENT_TRACING_ENV_VARIABLE, ) - +from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils +from azure.core.settings import settings +from azure.ai.projects.models import PromptAgentDefinition, PromptAgentDefinitionTextOptions from azure.ai.projects.telemetry._utils import ( - AZ_NAMESPACE, - AZ_NAMESPACE_VALUE, GEN_AI_AGENT_ID, GEN_AI_AGENT_NAME, GEN_AI_AGENT_VERSION, - GEN_AI_CONVERSATION_ID, GEN_AI_EVENT_CONTENT, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, - GEN_AI_RESPONSE_FINISH_REASONS, - GEN_AI_RESPONSE_ID, - GEN_AI_RESPONSE_MODEL, - GEN_AI_SYSTEM, - GEN_AI_USAGE_INPUT_TOKENS, - GEN_AI_USAGE_OUTPUT_TOKENS, SERVER_ADDRESS, GEN_AI_AGENT_TYPE, GEN_AI_SYSTEM_INSTRUCTION_EVENT, GEN_AI_AGENT_WORKFLOW_EVENT, - GEN_AI_CONVERSATION_ITEM_TYPE, - AZURE_AI_AGENTS_SYSTEM, AGENTS_PROVIDER, AGENT_TYPE_PROMPT, AGENT_TYPE_WORKFLOW, @@ -58,10 +38,10 @@ ) settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable -class TestAiAgentsInstrumentor(TestAiAgentsInstrumentorBase): +class TestAiAgentsInstrumentor(TestAiAgentsInstrumentorBase): # pylint: disable=too-many-public-methods """Tests for AI agents instrumentor.""" @pytest.fixture(scope="function") @@ -78,7 +58,7 @@ def instrument_without_content(self): yield self.cleanup() - def test_instrumentation(self, **kwargs): + def test_instrumentation(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" @@ -96,7 +76,7 @@ def test_instrumentation(self, **kwargs): os.environ.pop("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", None) assert exception_caught == False - def test_instrumenting_twice_does_not_cause_exception(self, **kwargs): + def test_instrumenting_twice_does_not_cause_exception(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" @@ -112,7 +92,7 @@ def test_instrumenting_twice_does_not_cause_exception(self, **kwargs): os.environ.pop("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", None) assert exception_caught == False - def test_uninstrumenting_uninstrumented_does_not_cause_exception(self, **kwargs): + def test_uninstrumenting_uninstrumented_does_not_cause_exception(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() exception_caught = False @@ -123,7 +103,7 @@ def test_uninstrumenting_uninstrumented_does_not_cause_exception(self, **kwargs) print(e) assert exception_caught == False - def test_uninstrumenting_twice_does_not_cause_exception(self, **kwargs): + def test_uninstrumenting_twice_does_not_cause_exception(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" @@ -178,7 +158,7 @@ def test_experimental_genai_tracing_gate(self, env_value: Optional[str], should_ from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor - from memory_trace_exporter import MemoryTraceExporter + from memory_trace_exporter import MemoryTraceExporter # pylint: disable=import-error tracer_provider = TracerProvider() trace._TRACER_PROVIDER = tracer_provider @@ -490,7 +470,7 @@ def _test_workflow_agent_creation_impl(self, use_events: bool, content_recording from azure.ai.projects.models import WorkflowAgentDefinition operation_group = "tracing" if content_recording_enabled else "agents" - with self.create_client(operation_group=operation_group, **kwargs) as project_client: + with self.create_client(operation_group=operation_group, allow_preview=True, **kwargs) as project_client: workflow_yaml = """ kind: workflow @@ -588,7 +568,7 @@ def test_workflow_agent_creation_with_tracing_content_recording_disabled_with_at def _test_agent_with_structured_output_with_instructions_impl( self, use_events: bool, content_recording_enabled: bool, **kwargs - ): + ): # pylint: disable=too-many-locals,too-many-statements """Implementation for agent with structured output and instructions test. :param use_events: If True, use events for messages. If False, use attributes. @@ -776,7 +756,7 @@ def test_agent_with_structured_output_with_instructions_content_recording_disabl def _test_agent_with_structured_output_without_instructions_impl( self, use_events: bool, content_recording_enabled: bool, **kwargs - ): + ): # pylint: disable=too-many-locals,too-many-statements """Implementation for agent with structured output but NO instructions test. :param use_events: If True, use events for messages. If False, use attributes. diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py index e1bbef6d0511..cdb7f5d46ed7 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor_async.py @@ -6,48 +6,28 @@ # cSpell:disable import os import pytest -from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from azure.ai.projects.models import PromptAgentDefinition, PromptAgentDefinitionTextOptions -from azure.ai.projects.models import ( - Reasoning, - FunctionTool, - # ResponseTextFormatConfigurationText, -) - +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error from devtools_testutils.aio import recorded_by_proxy_async - from test_base import servicePreparer -from test_ai_instrumentor_base import ( +from test_ai_instrumentor_base import ( # pylint: disable=import-error TestAiAgentsInstrumentorBase, - MessageCreationMode, CONTENT_TRACING_ENV_VARIABLE, ) - +from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils +from azure.core.settings import settings +from azure.ai.projects.models import PromptAgentDefinition, PromptAgentDefinitionTextOptions from azure.ai.projects.telemetry._utils import ( - AZ_NAMESPACE, - AZ_NAMESPACE_VALUE, GEN_AI_AGENT_ID, GEN_AI_AGENT_NAME, GEN_AI_AGENT_VERSION, - GEN_AI_CONVERSATION_ID, GEN_AI_EVENT_CONTENT, GEN_AI_OPERATION_NAME, GEN_AI_PROVIDER_NAME, GEN_AI_REQUEST_MODEL, - GEN_AI_RESPONSE_FINISH_REASONS, - GEN_AI_RESPONSE_ID, - GEN_AI_RESPONSE_MODEL, - GEN_AI_SYSTEM, - GEN_AI_USAGE_INPUT_TOKENS, - GEN_AI_USAGE_OUTPUT_TOKENS, SERVER_ADDRESS, GEN_AI_AGENT_TYPE, GEN_AI_SYSTEM_INSTRUCTION_EVENT, GEN_AI_AGENT_WORKFLOW_EVENT, - GEN_AI_CONVERSATION_ITEM_TYPE, - AZURE_AI_AGENTS_SYSTEM, AGENTS_PROVIDER, AGENT_TYPE_PROMPT, AGENT_TYPE_WORKFLOW, @@ -55,7 +35,7 @@ ) settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestAiAgentsInstrumentor(TestAiAgentsInstrumentorBase): @@ -241,7 +221,7 @@ async def _test_agent_creation_with_tracing_content_recording_disabled_impl(self else: # When using attributes and content recording disabled, verify empty structure from azure.ai.projects.telemetry._utils import GEN_AI_SYSTEM_MESSAGE - import json + import json # pylint: disable=reimported assert span.attributes is not None assert GEN_AI_SYSTEM_MESSAGE in span.attributes @@ -286,7 +266,7 @@ async def _test_workflow_agent_creation_impl(self, use_events: bool, content_rec from azure.ai.projects.models import WorkflowAgentDefinition operation_group = "tracing" if content_recording_enabled else "agents" - project_client = self.create_async_client(operation_group=operation_group, **kwargs) + project_client = self.create_async_client(operation_group=operation_group, allow_preview=True, **kwargs) async with project_client: workflow_yaml = """ @@ -383,7 +363,7 @@ async def test_workflow_agent_creation_with_tracing_content_recording_disabled_w async def _test_agent_with_structured_output_with_instructions_impl( self, use_events: bool, content_recording_enabled: bool, **kwargs - ): + ): # pylint: disable=too-many-locals,too-many-statements """Implementation for structured output with instructions test (async). :param use_events: If True, use events for messages. If False, use attributes. @@ -568,7 +548,7 @@ async def test_agent_with_structured_output_with_instructions_content_recording_ async def _test_agent_with_structured_output_without_instructions_impl( self, use_events: bool, content_recording_enabled: bool, **kwargs - ): + ): # pylint: disable=too-many-locals,too-many-statements """Implementation for structured output without instructions test (async). :param use_events: If True, use events for messages. If False, use attributes. diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_instrumentor_base.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_instrumentor_base.py index 44d97d825b62..945fa1da977e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_instrumentor_base.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_instrumentor_base.py @@ -11,10 +11,10 @@ from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor -from azure.ai.projects.telemetry import AIProjectInstrumentor -from gen_ai_trace_verifier import GenAiTraceVerifier -from memory_trace_exporter import MemoryTraceExporter +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from memory_trace_exporter import MemoryTraceExporter # pylint: disable=import-error from test_base import TestBase +from azure.ai.projects.telemetry import AIProjectInstrumentor CONTENT_TRACING_ENV_VARIABLE = "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT" EXPERIMENTAL_ENABLE_GENAI_TRACING_ENV_VARIABLE = "AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING" @@ -53,7 +53,7 @@ def setup_telemetry(self): os.environ[EXPERIMENTAL_ENABLE_GENAI_TRACING_ENV_VARIABLE] = "true" tracer_provider = TracerProvider() trace._TRACER_PROVIDER = tracer_provider - self.exporter = MemoryTraceExporter() + self.exporter = MemoryTraceExporter() # pylint: disable=attribute-defined-outside-init span_processor = SimpleSpanProcessor(self.exporter) tracer_provider.add_span_processor(span_processor) AIProjectInstrumentor().instrument() @@ -77,7 +77,7 @@ def _check_spans( event_contents: List[str], run_step_events: Optional[List[List[Dict[str, Any]]]] = None, has_annotations: bool = False, - ): + ): # pylint: disable=too-many-statements """Check the spans for correctness.""" spans = self.exporter.get_spans_by_name("create_agent my-agent") assert len(spans) == 1 diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py index 7556c4933eaa..6f15e3e0ad74 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py @@ -6,8 +6,16 @@ import os import json -import pytest from typing import Optional, Tuple +import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from openai import OpenAI +from devtools_testutils import recorded_by_proxy, RecordedTransport, set_custom_default_matcher, add_body_key_sanitizer +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_CHAT, @@ -18,20 +26,11 @@ _set_use_simple_tool_format, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from openai import OpenAI -from devtools_testutils import recorded_by_proxy, RecordedTransport, set_custom_default_matcher, add_body_key_sanitizer from azure.ai.projects.models import PromptAgentDefinition, FunctionTool - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable # Environment variable for binary data tracing BINARY_DATA_TRACING_ENV_VARIABLE = "AZURE_TRACING_GEN_AI_INCLUDE_BINARY_DATA" @@ -53,11 +52,11 @@ ) -class TestResponsesInstrumentor(TestAiAgentsInstrumentorBase): +class TestResponsesInstrumentor(TestAiAgentsInstrumentorBase): # pylint: disable=too-many-public-methods """Tests for ResponsesInstrumentor with real endpoints.""" @pytest.fixture(scope="session", autouse=True) - def configure_playback_matcher(self, test_proxy, add_sanitizers): + def configure_playback_matcher(self, test_proxy, add_sanitizers): # pylint: disable=unused-argument """Add body sanitizer and custom matchers for image_url in requests.""" # Sanitize image_url in request body to a consistent placeholder add_body_key_sanitizer(json_path="$..image_url", value="SANITIZED_IMAGE_DATA") @@ -82,7 +81,7 @@ def _get_openai_client_and_deployment(self, **kwargs) -> Tuple[OpenAI, str]: return openai_client, model_deployment_name - def test_instrumentation(self, **kwargs): + def test_instrumentation(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" @@ -100,7 +99,7 @@ def test_instrumentation(self, **kwargs): os.environ.pop("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", None) assert exception_caught == False - def test_instrumenting_twice_does_not_cause_exception(self, **kwargs): + def test_instrumenting_twice_does_not_cause_exception(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" @@ -116,7 +115,7 @@ def test_instrumenting_twice_does_not_cause_exception(self, **kwargs): os.environ.pop("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", None) assert exception_caught == False - def test_uninstrumenting_uninstrumented_does_not_cause_exception(self, **kwargs): + def test_uninstrumenting_uninstrumented_does_not_cause_exception(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() exception_caught = False @@ -127,7 +126,7 @@ def test_uninstrumenting_uninstrumented_does_not_cause_exception(self, **kwargs) print(e) assert exception_caught == False - def test_uninstrumenting_twice_does_not_cause_exception(self, **kwargs): + def test_uninstrumenting_twice_does_not_cause_exception(self): # Make sure code is not instrumented due to a previous test exception AIProjectInstrumentor().uninstrument() os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" @@ -168,24 +167,23 @@ def set_env_var(var_name, value): self.cleanup() @pytest.mark.parametrize( - "env_value, expected_enabled, expected_instrumented", + "env_value, expected_enabled", [ - (None, True, True), # Default: enabled and instrumented - ("true", True, True), # Explicitly enabled - ("True", True, True), # Case insensitive - ("TRUE", True, True), # Case insensitive - ("false", False, False), # Explicitly disabled - ("False", False, False), # Case insensitive - ("random", False, False), # Invalid value treated as false - ("0", False, False), # Numeric false - ("1", False, False), # Numeric true but not "true" + (None, True), # Default: enabled and instrumented + ("true", True), # Explicitly enabled + ("True", True), # Case insensitive + ("TRUE", True), # Case insensitive + ("false", False), # Explicitly disabled + ("False", False), # Case insensitive + ("random", False), # Invalid value treated as false + ("0", False), # Numeric false + ("1", False), # Numeric true but not "true" ], ) def test_instrumentation_environment_variable( self, env_value: Optional[str], expected_enabled: bool, - expected_instrumented: bool, ): def set_env_var(var_name, value): if value is None: @@ -460,10 +458,10 @@ def test_sync_non_streaming_without_content_recording_attributes(self, **kwargs) """Test synchronous non-streaming responses with content recording disabled (attribute mode).""" self._test_sync_non_streaming_without_content_recording_impl(False, **kwargs) - def _test_sync_streaming_with_content_recording_impl(self, use_events, **kwargs): + def _test_sync_streaming_with_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-statements """Implementation for testing synchronous streaming responses with content recording enabled.""" - from openai.types.responses.response_input_param import FunctionCallOutput - self.cleanup() _set_use_message_events(use_events) os.environ.update( @@ -656,7 +654,6 @@ def test_sync_conversations_create(self, **kwargs): with self.create_client(operation_group="tracing", **kwargs) as project_client: # Get the OpenAI client from the project client client = project_client.get_openai_client() - deployment_name = kwargs.get("foundry_model_name") # Create a conversation conversation = client.conversations.create() @@ -844,7 +841,7 @@ def test_no_instrumentation_no_spans(self): from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor - from memory_trace_exporter import MemoryTraceExporter + from memory_trace_exporter import MemoryTraceExporter # pylint: disable=import-error tracer_provider = TracerProvider() trace._TRACER_PROVIDER = tracer_provider @@ -972,7 +969,7 @@ def _test_sync_non_streaming_without_conversation_impl(self, use_events, **kwarg assert len(output_messages[0]["parts"][0]["content"]) > 0 assert "finish_reason" in output_messages[0] - def _test_sync_function_tool_with_content_recording_non_streaming_impl( + def _test_sync_function_tool_with_content_recording_non_streaming_impl( # pylint: disable=too-many-locals,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing synchronous function tool usage with content recording (non-streaming).""" @@ -1051,14 +1048,14 @@ def _test_sync_function_tool_with_content_recording_non_streaming_impl( ) # Second request - provide function results - response2 = client.responses.create( + _response2 = client.responses.create( conversation=conversation.id, input=input_list, extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, stream=False, ) - assert hasattr(response2, "output") - assert response2.output is not None + assert hasattr(_response2, "output") + assert _response2.output is not None # Cleanup project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) @@ -1231,7 +1228,7 @@ def _test_sync_function_tool_with_content_recording_non_streaming_impl( assert len(output_messages[0]["parts"][0]["content"]) > 0 assert "finish_reason" in output_messages[0] - def _test_sync_function_tool_with_content_recording_streaming_impl( + def _test_sync_function_tool_with_content_recording_streaming_impl( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing synchronous function tool usage with content recording (streaming).""" @@ -1555,7 +1552,7 @@ def test_sync_function_tool_with_content_recording_streaming_simple_format_attri False, use_simple_tool_call_format=True, **kwargs ) - def _test_sync_function_tool_without_content_recording_non_streaming_impl( + def _test_sync_function_tool_without_content_recording_non_streaming_impl( # pylint: disable=too-many-locals,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing synchronous function tool usage without content recording (non-streaming).""" @@ -1634,13 +1631,13 @@ def _test_sync_function_tool_without_content_recording_non_streaming_impl( ) # Second request - provide function results - response2 = client.responses.create( + _response2 = client.responses.create( conversation=conversation.id, input=input_list, extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, stream=False, ) - assert hasattr(response2, "output") + assert hasattr(_response2, "output") # Cleanup project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) @@ -1791,7 +1788,7 @@ def _test_sync_function_tool_without_content_recording_non_streaming_impl( assert input_messages[0]["parts"][0]["content"]["type"] == "function_call_output" assert "id" in input_messages[0]["parts"][0]["content"] - def _test_sync_function_tool_without_content_recording_streaming_impl( + def _test_sync_function_tool_without_content_recording_streaming_impl( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing synchronous function tool usage without content recording (streaming).""" @@ -2186,7 +2183,7 @@ def test_sync_function_tool_list_conversation_items_with_content_recording(self, ) # Second request - provide function results - response2 = client.responses.create( + _response2 = client.responses.create( conversation=conversation.id, input=input_list, extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, @@ -2336,7 +2333,7 @@ def test_sync_function_tool_list_conversation_items_without_content_recording(se ) # Second request - provide function results - response2 = client.responses.create( + _response2 = client.responses.create( conversation=conversation.id, input=input_list, extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, @@ -4612,7 +4609,7 @@ def test_responses_stream_method_with_content_recording(self, **kwargs): input="Write a short haiku about testing", ) as stream: # Iterate through events - for event in stream: + for _ in stream: pass # Process events # Get final response @@ -4665,7 +4662,7 @@ def test_responses_stream_method_without_content_recording(self, **kwargs): input="Write a short haiku about testing", ) as stream: # Iterate through events - for event in stream: + for _ in stream: pass # Process events # Get final response @@ -4739,7 +4736,7 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg input="What's the weather in Boston?", tools=[function_tool], ) as stream: - for event in stream: + for _ in stream: pass # Process events final_response = stream.get_final_response() @@ -4770,7 +4767,7 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg input=input_list, tools=[function_tool], ) as stream: - for event in stream: + for _ in stream: pass # Process events final_response = stream.get_final_response() @@ -4801,7 +4798,7 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg assert attributes_match == True # Validate second span (tool output + final response) - span2 = spans[1] + _span2 = spans[1] # pylint: disable=unused-variable @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @@ -4853,7 +4850,7 @@ def test_responses_stream_method_with_tools_without_content_recording(self, **kw input="What's the weather in Boston?", tools=[function_tool], ) as stream: - for event in stream: + for _ in stream: pass # Process events final_response = stream.get_final_response() @@ -4884,7 +4881,7 @@ def test_responses_stream_method_with_tools_without_content_recording(self, **kw input=input_list, tools=[function_tool], ) as stream: - for event in stream: + for _ in stream: pass # Process events final_response = stream.get_final_response() @@ -4942,9 +4939,11 @@ def test_responses_stream_method_with_tools_without_content_recording(self, **kw @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): + def test_workflow_agent_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test workflow agent with non-streaming and content recording enabled.""" - from azure.ai.projects.models import ( + from azure.ai.projects.models import ( # pylint: disable=reimported,redefined-outer-name WorkflowAgentDefinition, PromptAgentDefinition, ) @@ -4959,7 +4958,7 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): self.setup_telemetry() assert True == AIProjectInstrumentor().is_content_recording_enabled() - with self.create_client(operation_group="tracing", **kwargs) as project_client: + with self.create_client(operation_group="tracing", allow_preview=True, **kwargs) as project_client: deployment_name = kwargs.get("foundry_model_name") openai_client = project_client.get_openai_client() @@ -4968,7 +4967,7 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that create pre-school math question for student and check answer. + instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), @@ -4979,7 +4978,7 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -5144,7 +5143,9 @@ def test_workflow_agent_non_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): + def test_workflow_agent_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-statements """Test workflow agent with non-streaming and content recording disabled.""" from azure.ai.projects.models import WorkflowAgentDefinition @@ -5158,9 +5159,10 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): self.setup_telemetry() assert False == AIProjectInstrumentor().is_content_recording_enabled() - with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("foundry_model_name") - openai_client = project_client.get_openai_client() + with ( + self.create_client(operation_group="tracing", allow_preview=True, **kwargs) as project_client, + project_client.get_openai_client() as openai_client, + ): workflow_yaml = """ kind: workflow @@ -5180,7 +5182,7 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): conversation = openai_client.conversations.create() - response = openai_client.responses.create( + _response = openai_client.responses.create( conversation=conversation.id, extra_body={"agent_reference": {"name": workflow_agent.name, "type": "agent_reference"}}, input="Test workflow", @@ -5258,9 +5260,11 @@ def test_workflow_agent_non_streaming_without_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_workflow_agent_streaming_with_content_recording(self, **kwargs): + def test_workflow_agent_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test workflow agent with streaming and content recording enabled.""" - from azure.ai.projects.models import ( + from azure.ai.projects.models import ( # pylint: disable=reimported,redefined-outer-name WorkflowAgentDefinition, PromptAgentDefinition, ) @@ -5275,7 +5279,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): self.setup_telemetry() assert True == AIProjectInstrumentor().is_content_recording_enabled() - with self.create_client(operation_group="tracing", **kwargs) as project_client: + with self.create_client(operation_group="tracing", allow_preview=True, **kwargs) as project_client: deployment_name = kwargs.get("foundry_model_name") openai_client = project_client.get_openai_client() @@ -5284,7 +5288,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that create pre-school math question for student and check answer. + instructions="""You are a teacher that create pre-school math question for student and check answer. If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), @@ -5295,7 +5299,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -5463,7 +5467,7 @@ def test_workflow_agent_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_workflow_agent_streaming_without_content_recording(self, **kwargs): + def test_workflow_agent_streaming_without_content_recording(self, **kwargs): # pylint: disable=too-many-statements """Test workflow agent with streaming and content recording disabled.""" from azure.ai.projects.models import WorkflowAgentDefinition @@ -5477,9 +5481,10 @@ def test_workflow_agent_streaming_without_content_recording(self, **kwargs): self.setup_telemetry() assert False == AIProjectInstrumentor().is_content_recording_enabled() - with self.create_client(operation_group="tracing", **kwargs) as project_client: - deployment_name = kwargs.get("foundry_model_name") - openai_client = project_client.get_openai_client() + with ( + self.create_client(operation_group="tracing", allow_preview=True, **kwargs) as project_client, + project_client.get_openai_client() as openai_client, + ): workflow_yaml = """ kind: workflow diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py index 227b353fa800..a0c7c0f5ad6c 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -7,6 +7,14 @@ import os import json import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_CHAT, @@ -19,14 +27,6 @@ ) from azure.ai.projects.models import FunctionTool, PromptAgentDefinition from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import RecordedTransport -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) BINARY_DATA_TRACING_ENV_VARIABLE = "AZURE_TRACING_GEN_AI_INCLUDE_BINARY_DATA" @@ -34,10 +34,10 @@ TEST_IMAGE_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable -class TestResponsesInstrumentor(TestAiAgentsInstrumentorBase): +class TestResponsesInstrumentor(TestAiAgentsInstrumentorBase): # pylint: disable=too-many-public-methods """Tests for ResponsesInstrumentor with real endpoints (async).""" async def _test_async_non_streaming_with_content_recording_impl(self, use_events, **kwargs): @@ -277,7 +277,6 @@ async def test_async_conversations_create(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("foundry_model_name") async with project_client: # Get the OpenAI client from the project client @@ -388,7 +387,7 @@ async def test_async_list_conversation_items_with_content_recording(self, **kwar events_match = GenAiTraceVerifier().check_span_events(span, expected_events) assert events_match == True - async def _test_async_function_tool_with_content_recording_streaming_impl( + async def _test_async_function_tool_with_content_recording_streaming_impl( # pylint: disable=too-many-locals,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing asynchronous function tool usage with content recording (streaming). @@ -649,7 +648,7 @@ async def test_async_function_tool_with_content_recording_streaming_simple_forma False, use_simple_tool_call_format=True, **kwargs ) - async def _test_async_function_tool_without_content_recording_streaming_impl( + async def _test_async_function_tool_without_content_recording_streaming_impl( # pylint: disable=too-many-locals,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing asynchronous function tool usage without content recording (streaming). @@ -2593,7 +2592,7 @@ async def test_async_responses_stream_method_with_content_recording(self, **kwar input="Write a short haiku about testing", ) as stream: # Iterate through events - async for event in stream: + async for _ in stream: pass # Process events # Get final response @@ -2673,7 +2672,7 @@ async def test_async_responses_stream_method_without_content_recording(self, **k input="Write a short haiku about testing", ) as stream: # Iterate through events - async for event in stream: + async for _ in stream: pass # Process events # Get final response @@ -2724,7 +2723,7 @@ async def test_async_responses_stream_method_without_content_recording(self, **k events_match = GenAiTraceVerifier().check_span_events(span, expected_events) assert events_match == True - async def _test_async_responses_stream_method_with_tools_with_content_recording_impl( + async def _test_async_responses_stream_method_with_tools_with_content_recording_impl( # pylint: disable=too-many-locals,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing async responses.stream() method with function tools and content recording. @@ -2780,7 +2779,7 @@ async def _test_async_responses_stream_method_with_tools_with_content_recording_ input="What's the weather in Boston?", tools=[function_tool], ) as stream: - async for event in stream: + async for _ in stream: pass # Process events final_response = await stream.get_final_response() @@ -2811,7 +2810,7 @@ async def _test_async_responses_stream_method_with_tools_with_content_recording_ input=input_list, tools=[function_tool], ) as stream: - async for event in stream: + async for _ in stream: pass # Process events final_response = await stream.get_final_response() @@ -2957,7 +2956,7 @@ async def test_async_responses_stream_method_with_tools_with_content_recording_s False, use_simple_tool_call_format=True, **kwargs ) - async def _test_async_responses_stream_method_with_tools_without_content_recording_impl( + async def _test_async_responses_stream_method_with_tools_without_content_recording_impl( # pylint: disable=too-many-locals,too-many-statements self, use_events, use_simple_tool_call_format=False, **kwargs ): """Implementation for testing async responses.stream() method with function tools without content recording. @@ -3013,7 +3012,7 @@ async def _test_async_responses_stream_method_with_tools_without_content_recordi input="What\\'s the weather in Boston?", tools=[function_tool], ) as stream: - async for event in stream: + async for _ in stream: pass # Process events final_response = await stream.get_final_response() @@ -3044,7 +3043,7 @@ async def _test_async_responses_stream_method_with_tools_without_content_recordi input=input_list, tools=[function_tool], ) as stream: - async for event in stream: + async for _ in stream: pass # Process events final_response = await stream.get_final_response() @@ -3199,7 +3198,9 @@ async def test_async_responses_stream_method_with_tools_without_content_recordin @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_agent_non_streaming_with_content_recording(self, **kwargs): + async def test_async_workflow_agent_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-statements """Test async workflow agent with non-streaming and content recording enabled.""" from azure.ai.projects.models import WorkflowAgentDefinition @@ -3213,8 +3214,7 @@ async def test_async_workflow_agent_non_streaming_with_content_recording(self, * self.setup_telemetry() assert True == AIProjectInstrumentor().is_content_recording_enabled() - project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("foundry_model_name") + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) async with project_client: # Create a simple workflow agent @@ -3237,7 +3237,7 @@ async def test_async_workflow_agent_non_streaming_with_content_recording(self, * openai_client = project_client.get_openai_client() conversation = await openai_client.conversations.create() - response = await openai_client.responses.create( + _ = await openai_client.responses.create( conversation=conversation.id, extra_body={"agent_reference": {"name": workflow_agent.name, "type": "agent_reference"}}, input="Test workflow", @@ -3316,7 +3316,9 @@ async def test_async_workflow_agent_non_streaming_with_content_recording(self, * @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_agent_non_streaming_without_content_recording(self, **kwargs): + async def test_async_workflow_agent_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-statements """Test async workflow agent with non-streaming and content recording disabled.""" from azure.ai.projects.models import WorkflowAgentDefinition @@ -3330,8 +3332,7 @@ async def test_async_workflow_agent_non_streaming_without_content_recording(self self.setup_telemetry() assert False == AIProjectInstrumentor().is_content_recording_enabled() - project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("foundry_model_name") + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) async with project_client: workflow_yaml = """ @@ -3353,7 +3354,7 @@ async def test_async_workflow_agent_non_streaming_without_content_recording(self openai_client = project_client.get_openai_client() conversation = await openai_client.conversations.create() - response = await openai_client.responses.create( + _ = await openai_client.responses.create( conversation=conversation.id, extra_body={"agent_reference": {"name": workflow_agent.name, "type": "agent_reference"}}, input="Test workflow", @@ -3439,7 +3440,9 @@ async def test_async_workflow_agent_non_streaming_without_content_recording(self @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_agent_streaming_with_content_recording(self, **kwargs): + async def test_async_workflow_agent_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-statements """Test async workflow agent with streaming and content recording enabled.""" from azure.ai.projects.models import WorkflowAgentDefinition @@ -3453,8 +3456,7 @@ async def test_async_workflow_agent_streaming_with_content_recording(self, **kwa self.setup_telemetry() assert True == AIProjectInstrumentor().is_content_recording_enabled() - project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("foundry_model_name") + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) async with project_client: workflow_yaml = """ @@ -3560,7 +3562,9 @@ async def test_async_workflow_agent_streaming_with_content_recording(self, **kwa @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_agent_streaming_without_content_recording(self, **kwargs): + async def test_async_workflow_agent_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-statements """Test async workflow agent with streaming and content recording disabled.""" from azure.ai.projects.models import WorkflowAgentDefinition @@ -3574,8 +3578,7 @@ async def test_async_workflow_agent_streaming_without_content_recording(self, ** self.setup_telemetry() assert False == AIProjectInstrumentor().is_content_recording_enabled() - project_client = self.create_async_client(operation_group="tracing", **kwargs) - deployment_name = kwargs.get("foundry_model_name") + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) async with project_client: workflow_yaml = """ diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py index 592cf8b36b54..f9df1a9fb9be 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation.py @@ -9,6 +9,13 @@ import os import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils import recorded_by_proxy, RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -16,19 +23,11 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable @pytest.mark.skip( @@ -37,6 +36,8 @@ class TestResponsesInstrumentorBrowserAutomation(TestAiAgentsInstrumentorBase): """Tests for ResponsesInstrumentor with browser automation agents.""" + # pylint: disable=too-many-nested-blocks + # ======================================== # Sync Browser Automation Tests - Non-Streaming # ======================================== @@ -44,7 +45,9 @@ class TestResponsesInstrumentorBrowserAutomation(TestAiAgentsInstrumentorBase): @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_browser_automation_non_streaming_with_content_recording(self, **kwargs): + def test_sync_browser_automation_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test synchronous browser automation agent with non-streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -180,7 +183,9 @@ def test_sync_browser_automation_non_streaming_with_content_recording(self, **kw @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_browser_automation_non_streaming_without_content_recording(self, **kwargs): + def test_sync_browser_automation_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test synchronous browser automation agent with non-streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -310,7 +315,9 @@ def test_sync_browser_automation_non_streaming_without_content_recording(self, * @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_browser_automation_streaming_with_content_recording(self, **kwargs): + def test_sync_browser_automation_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test synchronous browser automation agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -438,7 +445,9 @@ def test_sync_browser_automation_streaming_with_content_recording(self, **kwargs @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_browser_automation_streaming_without_content_recording(self, **kwargs): + def test_sync_browser_automation_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test synchronous browser automation agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py index 55e3c949804f..f26d14d276bf 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_browser_automation_async.py @@ -8,7 +8,16 @@ """ import os +import json import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -16,22 +25,11 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import RecordedTransport from azure.ai.projects.models import PromptAgentDefinition - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) - -import json +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable @pytest.mark.skip( @@ -40,6 +38,8 @@ class TestResponsesInstrumentorBrowserAutomationAsync(TestAiAgentsInstrumentorBase): """Async tests for ResponsesInstrumentor with browser automation agents.""" + # pylint: disable=too-many-nested-blocks + # ======================================== # Async Browser Automation Tests - Non-Streaming # ======================================== @@ -47,7 +47,9 @@ class TestResponsesInstrumentorBrowserAutomationAsync(TestAiAgentsInstrumentorBa @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_browser_automation_non_streaming_with_content_recording(self, **kwargs): + async def test_async_browser_automation_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test asynchronous browser automation agent with non-streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -179,7 +181,9 @@ async def test_async_browser_automation_non_streaming_with_content_recording(sel @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_browser_automation_non_streaming_without_content_recording(self, **kwargs): + async def test_async_browser_automation_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test asynchronous browser automation agent with non-streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -305,7 +309,9 @@ async def test_async_browser_automation_non_streaming_without_content_recording( @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_browser_automation_streaming_with_content_recording(self, **kwargs): + async def test_async_browser_automation_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test asynchronous browser automation agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -430,7 +436,9 @@ async def test_async_browser_automation_streaming_with_content_recording(self, * @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_browser_automation_streaming_without_content_recording(self, **kwargs): + async def test_async_browser_automation_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks """Test asynchronous browser automation agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py index c282164e3fcf..219eba910511 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter.py @@ -8,8 +8,15 @@ """ import os -import pytest from io import BytesIO +import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils import recorded_by_proxy, RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -17,23 +24,15 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import ( PromptAgentDefinition, CodeInterpreterTool, AutoCodeInterpreterToolParam, ) - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestResponsesInstrumentorCodeInterpreter(TestAiAgentsInstrumentorBase): @@ -44,6 +43,8 @@ class TestResponsesInstrumentorCodeInterpreter(TestAiAgentsInstrumentorBase): with both content recording enabled and disabled, in both streaming and non-streaming modes. """ + # pylint: disable=too-many-nested-blocks + # ======================================== # Sync Code Interpreter Agent Tests - Non-Streaming # ======================================== @@ -51,7 +52,9 @@ class TestResponsesInstrumentorCodeInterpreter(TestAiAgentsInstrumentorBase): @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwargs): + def test_sync_code_interpreter_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test synchronous Code Interpreter agent with content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -101,7 +104,7 @@ def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwar conversation = openai_client.conversations.create() # Ask question that triggers code interpreter - response = openai_client.responses.create( + _ = openai_client.responses.create( conversation=conversation.id, input="Calculate the average operating profit from the transportation data", extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, @@ -109,7 +112,7 @@ def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwar # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans @@ -239,7 +242,9 @@ def test_sync_code_interpreter_non_streaming_with_content_recording(self, **kwar @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_code_interpreter_non_streaming_without_content_recording(self, **kwargs): + def test_sync_code_interpreter_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test synchronous Code Interpreter agent with content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -288,7 +293,7 @@ def test_sync_code_interpreter_non_streaming_without_content_recording(self, **k conversation = openai_client.conversations.create() # Ask question that triggers code interpreter - response = openai_client.responses.create( + _ = openai_client.responses.create( conversation=conversation.id, input="Calculate the average operating profit from the transportation data", extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, @@ -296,7 +301,7 @@ def test_sync_code_interpreter_non_streaming_without_content_recording(self, **k # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans @@ -430,7 +435,9 @@ def test_sync_code_interpreter_non_streaming_without_content_recording(self, **k @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_code_interpreter_streaming_with_content_recording(self, **kwargs): + def test_sync_code_interpreter_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test synchronous Code Interpreter agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -492,7 +499,7 @@ def test_sync_code_interpreter_streaming_with_content_recording(self, **kwargs): # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans @@ -621,7 +628,9 @@ def test_sync_code_interpreter_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_code_interpreter_streaming_without_content_recording(self, **kwargs): + def test_sync_code_interpreter_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test synchronous Code Interpreter agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -683,7 +692,7 @@ def test_sync_code_interpreter_streaming_without_content_recording(self, **kwarg # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py index ac91f4b83565..a6d49b60eb0c 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_code_interpreter_async.py @@ -8,8 +8,16 @@ """ import os -import pytest from io import BytesIO +import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -17,24 +25,15 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import RecordedTransport from azure.ai.projects.models import ( PromptAgentDefinition, CodeInterpreterTool, AutoCodeInterpreterToolParam, ) - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestResponsesInstrumentorCodeInterpreterAsync(TestAiAgentsInstrumentorBase): @@ -45,6 +44,8 @@ class TestResponsesInstrumentorCodeInterpreterAsync(TestAiAgentsInstrumentorBase with both content recording enabled and disabled, in both streaming and non-streaming modes. """ + # pylint: disable=too-many-nested-blocks + # ======================================== # Async Code Interpreter Agent Tests - Non-Streaming # ======================================== @@ -52,7 +53,9 @@ class TestResponsesInstrumentorCodeInterpreterAsync(TestAiAgentsInstrumentorBase @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_code_interpreter_non_streaming_with_content_recording(self, **kwargs): + async def test_async_code_interpreter_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test asynchronous Code Interpreter agent with content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -101,7 +104,7 @@ async def test_async_code_interpreter_non_streaming_with_content_recording(self, conversation = await openai_client.conversations.create() # Ask question that triggers code interpreter - response = await openai_client.responses.create( + _ = await openai_client.responses.create( conversation=conversation.id, input="Calculate the average operating profit from the transportation data", extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, @@ -109,7 +112,7 @@ async def test_async_code_interpreter_non_streaming_with_content_recording(self, # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans @@ -239,7 +242,9 @@ async def test_async_code_interpreter_non_streaming_with_content_recording(self, @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_code_interpreter_non_streaming_without_content_recording(self, **kwargs): + async def test_async_code_interpreter_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test asynchronous Code Interpreter agent with content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -288,7 +293,7 @@ async def test_async_code_interpreter_non_streaming_without_content_recording(se conversation = await openai_client.conversations.create() # Ask question that triggers code interpreter - response = await openai_client.responses.create( + _ = await openai_client.responses.create( conversation=conversation.id, input="Calculate the average operating profit from the transportation data", extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, @@ -296,7 +301,7 @@ async def test_async_code_interpreter_non_streaming_without_content_recording(se # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans @@ -430,7 +435,9 @@ async def test_async_code_interpreter_non_streaming_without_content_recording(se @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_code_interpreter_streaming_with_content_recording(self, **kwargs): + async def test_async_code_interpreter_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test asynchronous Code Interpreter agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -492,7 +499,7 @@ async def test_async_code_interpreter_streaming_with_content_recording(self, **k # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans @@ -621,7 +628,9 @@ async def test_async_code_interpreter_streaming_with_content_recording(self, **k @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_code_interpreter_streaming_without_content_recording(self, **kwargs): + async def test_async_code_interpreter_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements,too-many-nested-blocks """Test asynchronous Code Interpreter agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -683,7 +692,7 @@ async def test_async_code_interpreter_streaming_without_content_recording(self, # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py index 17b4ba9285f3..6af63135e120 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search.py @@ -8,8 +8,15 @@ """ import os -import pytest from io import BytesIO +import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils import recorded_by_proxy, RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -17,28 +24,24 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestResponsesInstrumentorFileSearch(TestAiAgentsInstrumentorBase): """Tests for ResponsesInstrumentor with File Search tool.""" + # pylint: disable=too-many-nested-blocks + @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_file_search_non_streaming_with_content_recording(self, **kwargs): + def test_sync_file_search_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test synchronous File Search agent with non-streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -113,7 +116,7 @@ def test_sync_file_search_non_streaming_with_content_recording(self, **kwargs): # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Just iterate to consume items # Check spans @@ -247,7 +250,9 @@ def test_sync_file_search_non_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_file_search_non_streaming_without_content_recording(self, **kwargs): + def test_sync_file_search_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test synchronous File Search agent with non-streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -322,7 +327,7 @@ def test_sync_file_search_non_streaming_without_content_recording(self, **kwargs # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Just iterate to consume items # Check spans @@ -454,7 +459,9 @@ def test_sync_file_search_non_streaming_without_content_recording(self, **kwargs @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_file_search_streaming_with_content_recording(self, **kwargs): + def test_sync_file_search_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test synchronous File Search agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -531,7 +538,7 @@ def test_sync_file_search_streaming_with_content_recording(self, **kwargs): # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans @@ -659,7 +666,9 @@ def test_sync_file_search_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_file_search_streaming_without_content_recording(self, **kwargs): + def test_sync_file_search_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test synchronous File Search agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -736,7 +745,7 @@ def test_sync_file_search_streaming_without_content_recording(self, **kwargs): # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py index 704d6bbb7117..33f0d6e4edd7 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_file_search_async.py @@ -8,8 +8,16 @@ """ import os -import pytest from io import BytesIO +import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -17,29 +25,24 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestResponsesInstrumentorFileSearchAsync(TestAiAgentsInstrumentorBase): """Async tests for ResponsesInstrumentor with File Search tool.""" + # pylint: disable=too-many-nested-blocks + @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_file_search_non_streaming_with_content_recording(self, **kwargs): + async def test_async_file_search_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test asynchronous File Search agent with non-streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -114,7 +117,7 @@ async def test_async_file_search_non_streaming_with_content_recording(self, **kw # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Just iterate to consume items # Check spans @@ -248,7 +251,9 @@ async def test_async_file_search_non_streaming_with_content_recording(self, **kw @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_file_search_non_streaming_without_content_recording(self, **kwargs): + async def test_async_file_search_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test asynchronous File Search agent with non-streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -323,7 +328,7 @@ async def test_async_file_search_non_streaming_without_content_recording(self, * # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Just iterate to consume items # Check spans @@ -455,7 +460,9 @@ async def test_async_file_search_non_streaming_without_content_recording(self, * @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_file_search_streaming_with_content_recording(self, **kwargs): + async def test_async_file_search_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test asynchronous File Search agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -532,7 +539,7 @@ async def test_async_file_search_streaming_with_content_recording(self, **kwargs # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans @@ -660,7 +667,9 @@ async def test_async_file_search_streaming_with_content_recording(self, **kwargs @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_file_search_streaming_without_content_recording(self, **kwargs): + async def test_async_file_search_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Test asynchronous File Search agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -737,7 +746,7 @@ async def test_async_file_search_streaming_without_content_recording(self, **kwa # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py index 0ac08ad35e2a..7a47c7f08936 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp.py @@ -9,6 +9,14 @@ import os import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils import recorded_by_proxy, RecordedTransport +from openai.types.responses.response_input_param import McpApprovalResponse +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -16,30 +24,25 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, MCPTool -from openai.types.responses.response_input_param import McpApprovalResponse - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestResponsesInstrumentorMCP(TestAiAgentsInstrumentorBase): """Tests for ResponsesInstrumentor with MCP agents.""" + # pylint: disable=too-many-nested-blocks + # ======================================== # Sync MCP Agent Tests - Non-Streaming # ======================================== - def _test_sync_mcp_non_streaming_with_content_recording_impl(self, use_events, **kwargs): + def _test_sync_mcp_non_streaming_with_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing synchronous MCP agent with non-streaming and content recording enabled. Args: @@ -117,7 +120,7 @@ def _test_sync_mcp_non_streaming_with_content_recording_impl(self, use_events, * # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Iterate to consume items # Check spans @@ -369,7 +372,9 @@ def test_sync_mcp_non_streaming_with_content_recording_attributes(self, **kwargs """Test synchronous MCP agent with non-streaming and content recording enabled (attribute-based messages).""" self._test_sync_mcp_non_streaming_with_content_recording_impl(False, **kwargs) - def _test_sync_mcp_non_streaming_without_content_recording_impl(self, use_events, **kwargs): + def _test_sync_mcp_non_streaming_without_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing synchronous MCP agent with non-streaming and content recording disabled. Args: @@ -447,7 +452,7 @@ def _test_sync_mcp_non_streaming_without_content_recording_impl(self, use_events # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Just iterate to consume items # Check spans @@ -686,7 +691,9 @@ def test_sync_mcp_non_streaming_without_content_recording_attributes(self, **kwa # Sync MCP Agent Tests - Streaming # ======================================== - def _test_sync_mcp_streaming_with_content_recording_impl(self, use_events, **kwargs): + def _test_sync_mcp_streaming_with_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing synchronous MCP agent with streaming and content recording enabled. Args: @@ -769,7 +776,7 @@ def _test_sync_mcp_streaming_with_content_recording_impl(self, use_events, **kwa # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans @@ -962,7 +969,9 @@ def test_sync_mcp_streaming_with_content_recording_attributes(self, **kwargs): """Test synchronous MCP agent with streaming and content recording enabled (attribute-based messages).""" self._test_sync_mcp_streaming_with_content_recording_impl(False, **kwargs) - def _test_sync_mcp_streaming_without_content_recording_impl(self, use_events, **kwargs): + def _test_sync_mcp_streaming_without_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing synchronous MCP agent with streaming and content recording disabled. Args: @@ -1045,7 +1054,7 @@ def _test_sync_mcp_streaming_without_content_recording_impl(self, use_events, ** # Explicitly call and iterate through conversation items items = openai_client.conversations.items.list(conversation_id=conversation.id) - for item in items: + for _ in items: pass # Check spans diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py index e6401924773a..71ad6dfad50b 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_mcp_async.py @@ -9,6 +9,15 @@ import os import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from openai.types.responses.response_input_param import McpApprovalResponse +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -16,31 +25,25 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, MCPTool -from openai.types.responses.response_input_param import McpApprovalResponse - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable class TestResponsesInstrumentorMCPAsync(TestAiAgentsInstrumentorBase): """Async tests for ResponsesInstrumentor with MCP agents.""" + # pylint: disable=too-many-nested-blocks + # ======================================== # Async MCP Agent Tests - Non-Streaming # ======================================== - async def _test_async_mcp_non_streaming_with_content_recording_impl(self, use_events, **kwargs): + async def _test_async_mcp_non_streaming_with_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing asynchronous MCP agent with non-streaming and content recording enabled. Args: @@ -118,7 +121,7 @@ async def _test_async_mcp_non_streaming_with_content_recording_impl(self, use_ev # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Just iterate to consume items # Check spans @@ -369,7 +372,9 @@ async def test_async_mcp_non_streaming_with_content_recording_attributes(self, * """Test asynchronous MCP agent with non-streaming and content recording enabled (attribute-based messages).""" await self._test_async_mcp_non_streaming_with_content_recording_impl(False, **kwargs) - async def _test_async_mcp_non_streaming_without_content_recording_impl(self, use_events, **kwargs): + async def _test_async_mcp_non_streaming_without_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing asynchronous MCP agent with non-streaming and content recording disabled. Args: @@ -447,7 +452,7 @@ async def _test_async_mcp_non_streaming_without_content_recording_impl(self, use # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Just iterate to consume items # Check spans @@ -688,7 +693,9 @@ async def test_async_mcp_non_streaming_without_content_recording_attributes(self # Async MCP Agent Tests - Streaming # ======================================== - async def _test_async_mcp_streaming_with_content_recording_impl(self, use_events, **kwargs): + async def _test_async_mcp_streaming_with_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing asynchronous MCP agent with streaming and content recording enabled. Args: @@ -771,7 +778,7 @@ async def _test_async_mcp_streaming_with_content_recording_impl(self, use_events # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans @@ -967,7 +974,9 @@ async def test_async_mcp_streaming_with_content_recording_attributes(self, **kwa """Test asynchronous MCP agent with streaming and content recording enabled (attribute-based messages).""" await self._test_async_mcp_streaming_with_content_recording_impl(False, **kwargs) - async def _test_async_mcp_streaming_without_content_recording_impl(self, use_events, **kwargs): + async def _test_async_mcp_streaming_without_content_recording_impl( + self, use_events, **kwargs + ): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """Implementation for testing asynchronous MCP agent with streaming and content recording disabled. Args: @@ -1050,7 +1059,7 @@ async def _test_async_mcp_streaming_without_content_recording_impl(self, use_eve # Explicitly call and iterate through conversation items items = await openai_client.conversations.items.list(conversation_id=conversation.id) - async for item in items: + async for _ in items: pass # Check spans diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py index 57f17169897b..b4f5b187c865 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_metrics.py @@ -5,23 +5,23 @@ # ------------------------------------ import os -import pytest from typing import Tuple -from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils -from azure.core.settings import settings +import pytest from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import InMemoryMetricReader from opentelemetry import metrics from openai import OpenAI from test_base import servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport -from test_ai_instrumentor_base import ( +from test_ai_instrumentor_base import ( # pylint: disable=import-error TestAiAgentsInstrumentorBase, CONTENT_TRACING_ENV_VARIABLE, ) +from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable # Set up global metrics collection like in the sample global_metric_reader = InMemoryMetricReader() @@ -182,7 +182,7 @@ def test_metrics_collection_conversation_create(self, **kwargs): assert True == AIProjectInstrumentor().is_instrumented() # Get OpenAI client and deployment - client, deployment_name = self._get_openai_client_and_deployment(**kwargs) + client, _ = self._get_openai_client_and_deployment(**kwargs) # Create a conversation conversation = client.conversations.create() diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py index 7e390d3e3a3a..b2c117a75ba5 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow.py @@ -9,6 +9,13 @@ import os import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils import recorded_by_proxy, RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -16,22 +23,14 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import ( PromptAgentDefinition, WorkflowAgentDefinition, ) - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable def checkWorkflowEventContents(content, content_recording_enabled): @@ -191,7 +190,9 @@ def _create_student_teacher_workflow(self, project_client, student_agent, teache @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): + def test_sync_workflow_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test synchronous workflow agent with non-streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -205,7 +206,7 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_client(operation_group="tracing", **kwargs) + project_client = self.create_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -217,8 +218,8 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -228,7 +229,7 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -314,7 +315,7 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, True) @@ -356,7 +357,9 @@ def test_sync_workflow_non_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): + def test_sync_workflow_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test synchronous workflow agent with non-streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -370,7 +373,7 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): assert not AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_client(operation_group="tracing", **kwargs) + project_client = self.create_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -382,8 +385,8 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -393,7 +396,7 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -476,7 +479,7 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, False) @@ -523,7 +526,9 @@ def test_sync_workflow_non_streaming_without_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_workflow_streaming_with_content_recording(self, **kwargs): + def test_sync_workflow_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test synchronous workflow agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -537,7 +542,7 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_client(operation_group="tracing", **kwargs) + project_client = self.create_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -549,8 +554,8 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -560,7 +565,7 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -648,7 +653,7 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, True) @@ -691,7 +696,9 @@ def test_sync_workflow_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sync_workflow_streaming_without_content_recording(self, **kwargs): + def test_sync_workflow_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test synchronous workflow agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -705,7 +712,7 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): assert not AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_client(operation_group="tracing", **kwargs) + project_client = self.create_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -717,8 +724,8 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -728,7 +735,7 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -816,7 +823,7 @@ def test_sync_workflow_streaming_without_content_recording(self, **kwargs): try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, False) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py index 75808d2f8a2d..4125772e8a42 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py @@ -7,8 +7,17 @@ Async tests for ResponsesInstrumentor with workflow agents. """ +import json import os import pytest +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from devtools_testutils.aio import recorded_by_proxy_async +from devtools_testutils import RecordedTransport +from test_base import servicePreparer +from test_ai_instrumentor_base import ( # pylint: disable=import-error + TestAiAgentsInstrumentorBase, + CONTENT_TRACING_ENV_VARIABLE, +) from azure.ai.projects.telemetry import AIProjectInstrumentor, _utils from azure.ai.projects.telemetry._utils import ( OPERATION_NAME_INVOKE_AGENT, @@ -16,25 +25,14 @@ _set_use_message_events, RESPONSES_PROVIDER, ) -from azure.core.settings import settings -from gen_ai_trace_verifier import GenAiTraceVerifier -from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import RecordedTransport from azure.ai.projects.models import ( PromptAgentDefinition, WorkflowAgentDefinition, ) - -from test_base import servicePreparer -from test_ai_instrumentor_base import ( - TestAiAgentsInstrumentorBase, - CONTENT_TRACING_ENV_VARIABLE, -) - -import json +from azure.core.settings import settings settings.tracing_implementation = "OpenTelemetry" -_utils._span_impl_type = settings.tracing_implementation() +_utils._span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable def checkWorkflowEventContents(content, content_recording_enabled): @@ -190,7 +188,9 @@ async def _create_student_teacher_workflow(self, project_client, student_agent, @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_non_streaming_with_content_recording(self, **kwargs): + async def test_async_workflow_non_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test asynchronous workflow agent with non-streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -204,7 +204,7 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg assert AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_async_client(operation_group="tracing", **kwargs) + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -216,8 +216,8 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -227,7 +227,7 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -306,7 +306,7 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg continue try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, True) @@ -351,7 +351,9 @@ async def test_async_workflow_non_streaming_with_content_recording(self, **kwarg @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_non_streaming_without_content_recording(self, **kwargs): + async def test_async_workflow_non_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test asynchronous workflow agent with non-streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -365,7 +367,7 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw assert not AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_async_client(operation_group="tracing", **kwargs) + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -377,8 +379,8 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -388,7 +390,7 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -467,7 +469,7 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw continue try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, False) @@ -516,7 +518,9 @@ async def test_async_workflow_non_streaming_without_content_recording(self, **kw @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_streaming_with_content_recording(self, **kwargs): + async def test_async_workflow_streaming_with_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test asynchronous workflow agent with streaming and content recording enabled.""" self.cleanup() _set_use_message_events(True) @@ -530,7 +534,7 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): assert AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_async_client(operation_group="tracing", **kwargs) + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -542,8 +546,8 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -553,7 +557,7 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -637,7 +641,7 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): continue try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, True) @@ -682,7 +686,9 @@ async def test_async_workflow_streaming_with_content_recording(self, **kwargs): @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_async_workflow_streaming_without_content_recording(self, **kwargs): + async def test_async_workflow_streaming_without_content_recording( + self, **kwargs + ): # pylint: disable=too-many-locals,too-many-statements """Test asynchronous workflow agent with streaming and content recording disabled.""" self.cleanup() _set_use_message_events(True) @@ -696,7 +702,7 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs assert not AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_async_client(operation_group="tracing", **kwargs) + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None @@ -708,8 +714,8 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs agent_name="teacher-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a teacher that creates pre-school math questions for students and checks answers. - If the answer is correct, you stop the conversation by saying [COMPLETE]. + instructions="""You are a teacher that creates pre-school math questions for students and checks answers. + If the answer is correct, you stop the conversation by saying [COMPLETE]. If the answer is wrong, you ask student to fix it.""", ), ) @@ -719,7 +725,7 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs agent_name="student-agent", definition=PromptAgentDefinition( model=deployment_name, - instructions="""You are a student who answers questions from the teacher. + instructions="""You are a student who answers questions from the teacher. When the teacher gives you a question, you answer it.""", ), ) @@ -803,7 +809,7 @@ async def test_async_workflow_streaming_without_content_recording(self, **kwargs continue try: data = json.loads(event_content) - except Exception: + except Exception: # pylint: disable=broad-exception-caught continue if isinstance(data, list) and any(entry.get("role") == "workflow" for entry in data): checkWorkflowEventContents(event_content, False) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator.py index 7aa983af2106..568821c8e09f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator.py @@ -4,13 +4,15 @@ # ------------------------------------ """Tests for the trace_function decorator with synchronous functions.""" +# pylint: disable=unused-argument + import pytest from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from memory_trace_exporter import MemoryTraceExporter # pylint: disable=import-error from azure.ai.projects.telemetry._trace_function import trace_function -from gen_ai_trace_verifier import GenAiTraceVerifier -from memory_trace_exporter import MemoryTraceExporter class TestTraceFunctionDecorator: @@ -21,7 +23,7 @@ def setup_telemetry(self): """Setup telemetry for tests.""" tracer_provider = TracerProvider() trace._TRACER_PROVIDER = tracer_provider - self.exporter = MemoryTraceExporter() + self.exporter = MemoryTraceExporter() # pylint: disable=attribute-defined-outside-init span_processor = SimpleSpanProcessor(self.exporter) tracer_provider.add_span_processor(span_processor) yield diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator_async.py index 9e4824859198..250bbfc58eff 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_trace_function_decorator_async.py @@ -4,24 +4,26 @@ # ------------------------------------ """Tests for the trace_function decorator with asynchronous functions.""" +# pylint: disable=unused-argument + import pytest from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor +from gen_ai_trace_verifier import GenAiTraceVerifier # pylint: disable=import-error +from memory_trace_exporter import MemoryTraceExporter # pylint: disable=import-error from azure.ai.projects.telemetry._trace_function import trace_function -from gen_ai_trace_verifier import GenAiTraceVerifier -from memory_trace_exporter import MemoryTraceExporter class TestTraceFunctionDecoratorAsync: """Tests for trace_function decorator with asynchronous functions.""" @pytest.fixture(scope="function") - def setup_telemetry(self): + def setup_telemetry(self): # pylint: disable=attribute-defined-outside-init """Setup telemetry for tests.""" tracer_provider = TracerProvider() trace._TRACER_PROVIDER = tracer_provider - self.exporter = MemoryTraceExporter() + self.exporter = MemoryTraceExporter() # pylint: disable=attribute-defined-outside-init span_processor = SimpleSpanProcessor(self.exporter) tracer_provider.add_span_processor(span_processor) yield @@ -199,7 +201,7 @@ async def test_async_function_with_boolean_parameters(self, setup_telemetry): async def check_status_async(is_active: bool, is_verified: bool) -> str: if is_active and is_verified: return "approved" - elif is_active: + if is_active: return "pending" return "inactive" @@ -359,7 +361,7 @@ async def process_data_async(name: str, count: int, active: bool, scores: list) assert attributes_match is True @pytest.mark.asyncio - async def test_async_function_with_default_parameters(self, setup_telemetry): + async def test_async_function_with_default_parameters(self, setup_telemetry): # pylint: disable=unused-argument """Test decorator with async function using default parameters.""" @trace_function() @@ -386,7 +388,7 @@ async def create_user_async(name: str, role: str = "user", active: bool = True) assert attributes_match is True @pytest.mark.asyncio - async def test_async_function_list_return_value(self, setup_telemetry): + async def test_async_function_list_return_value(self, setup_telemetry): # pylint: disable=unused-argument """Test decorator with async function returning a list.""" @trace_function() diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py index 6cc81e4f5553..d013c33e9847 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_create_version_exception.py @@ -5,7 +5,6 @@ # ------------------------------------ # cSpell:disable -import functools import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py index 44c8c315389e..4a8727c0e5e8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py @@ -13,7 +13,6 @@ TextResponseFormatJsonSchema, PromptAgentDefinitionTextOptions, ) -import pytest class TestAgentResponsesCrud(TestBase): @@ -75,7 +74,7 @@ def test_agent_responses_crud(self, **kwargs): print(f"Response id: {response.id}, output text: {response.output_text}") assert "5280" in response.output_text or "5,280" in response.output_text - items = openai_client.conversations.items.create( + _ = openai_client.conversations.items.create( conversation.id, items=[{"type": "message", "role": "user", "content": "And how many meters?"}], ) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py index 27151090e10a..22aeff937bc5 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py @@ -14,7 +14,6 @@ TextResponseFormatJsonSchema, PromptAgentDefinitionTextOptions, ) -import pytest class TestAgentResponsesCrudAsync(TestBase): @@ -76,7 +75,7 @@ async def test_agent_responses_crud_async(self, **kwargs): conversation_id=conversation.id, items=[{"type": "message", "role": "user", "content": "And how many meters?"}], ) - print(f"Added a second user message to the conversation") + print("Added a second user message to the conversation") response = await openai_client.responses.create( conversation=conversation.id, diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py index 2e6451effb59..702d7786fbdb 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud.py @@ -9,7 +9,6 @@ from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy from azure.ai.projects.models import PromptAgentDefinition, AgentDetails, AgentVersionDetails -import pytest class TestAgentCrud(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py index 6eb878e729b8..7dae5621f724 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agents_crud_async.py @@ -9,7 +9,6 @@ from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from azure.ai.projects.models import PromptAgentDefinition, AgentDetails, AgentVersionDetails -import pytest class TestAgentCrudAsync(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud.py index 448cf90f4b49..a597d78a64d0 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud.py @@ -7,7 +7,6 @@ from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport -import pytest # from azure.ai.projects.models import ResponsesUserMessageItemParam, ItemContentInputText @@ -88,7 +87,7 @@ def test_conversation_crud(self, **kwargs): metadata = {"key1": "value1", "key2": "value2"} conversation = client.conversations.update(conversation_id=conversation1.id, metadata=metadata) TestBase._validate_conversation(conversation, expected_id=conversation1.id, expected_metadata=metadata) - print(f"Conversation updated") + print("Conversation updated") conversation = client.conversations.retrieve(conversation_id=conversation1.id) TestBase._validate_conversation(conversation) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud_async.py index b8e26610aec5..b8638956cccd 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_crud_async.py @@ -8,7 +8,6 @@ from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport -import pytest # from azure.ai.projects.models import ResponsesUserMessageItemParam, ItemContentInputText @@ -66,7 +65,7 @@ async def test_conversation_crud_async(self, **kwargs): metadata = {"key1": "value1", "key2": "value2"} conversation = await client.conversations.update(conversation_id=conversation1.id, metadata=metadata) TestBase._validate_conversation(conversation, expected_id=conversation1.id, expected_metadata=metadata) - print(f"Conversation updated") + print("Conversation updated") conversation = await client.conversations.retrieve(conversation_id=conversation1.id) TestBase._validate_conversation(conversation) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud.py index d1b43a519465..e72730729d6d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud.py @@ -7,7 +7,6 @@ from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport -import pytest class TestConversationItemsCrud(TestBase): @@ -45,7 +44,7 @@ def test_conversation_items_crud(self, **kwargs): print(f"Created conversation (id: {conversation.id})") try: - print(f"Test create_items") + print("Test create_items") # Create items with short-form and long-form text message as Dict # See https://platform.openai.com/docs/api-reference/conversations/create-items items = [ @@ -58,7 +57,7 @@ def test_conversation_items_crud(self, **kwargs): ) assert items.has_more is False item_list = items.data - print(f"Created item with short-form and long form text messages as Dict") + print("Created item with short-form and long form text messages as Dict") assert len(item_list) == 2 self._validate_conversation_item( item_list[0], @@ -106,7 +105,7 @@ def test_conversation_items_crud(self, **kwargs): # item3_id = item_list[0].id # item4_id = item_list[1].id - print(f"Test retrieve item") + print("Test retrieve item") item = client.conversations.items.retrieve(conversation_id=conversation.id, item_id=item1_id) self._validate_conversation_item( item, @@ -117,14 +116,14 @@ def test_conversation_items_crud(self, **kwargs): expected_content_text="first message", ) - print(f"Test list items") + print("Test list items") item_count = 0 for item in client.conversations.items.list(conversation.id): item_count += 1 self._validate_conversation_item(item) assert item_count == 2 - print(f"Test delete item") + print("Test delete item") # result = client.conversations.items.delete(conversation_id=conversation.id, item_id=item4_id) # assert result.id == conversation.id result = client.conversations.items.delete(conversation_id=conversation.id, item_id=item2_id) diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud_async.py index e3da364d1c4c..830ff107d458 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_conversation_items_crud_async.py @@ -8,7 +8,6 @@ from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport -import pytest class TestConversationItemsCrudAsync(TestBase): @@ -24,7 +23,7 @@ async def test_conversation_items_crud_async(self, **kwargs): print(f"Created conversation (id: {conversation.id})") try: - print(f"Test create_items") + print("Test create_items") # Create items with short-form and long-form text message as Dict # See https://platform.openai.com/docs/api-reference/conversations/create-items items = [ @@ -37,7 +36,7 @@ async def test_conversation_items_crud_async(self, **kwargs): ) assert items.has_more is False item_list = items.data - print(f"Created item with short-form and long form text messages as Dict") + print("Created item with short-form and long form text messages as Dict") assert len(item_list) == 2 self._validate_conversation_item( item_list[0], @@ -85,7 +84,7 @@ async def test_conversation_items_crud_async(self, **kwargs): # item3_id = item_list[0].id # item4_id = item_list[1].id - print(f"Test retrieve item") + print("Test retrieve item") item = await client.conversations.items.retrieve(conversation_id=conversation.id, item_id=item1_id) self._validate_conversation_item( item, @@ -96,14 +95,14 @@ async def test_conversation_items_crud_async(self, **kwargs): expected_content_text="first message", ) - print(f"Test list items") + print("Test list items") item_count = 0 async for item in client.conversations.items.list(conversation.id): item_count += 1 self._validate_conversation_item(item) assert item_count == 2 - print(f"Test delete item") + print("Test delete item") # result = await client.conversations.items.delete(conversation_id=conversation.id, item_id=item4_id) # assert result.id == conversation.id result = await client.conversations.items.delete(conversation_id=conversation.id, item_id=item2_id) diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py index 4ef0e6b845f6..6d49b63dc4d8 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_code_interpreter_and_function.py @@ -5,8 +5,6 @@ # ------------------------------------ # cSpell:disable -import pytest - """ Multi-Tool Tests: Code Interpreter + Function Tool @@ -14,7 +12,6 @@ All tests use the same tool combination but different inputs and workflows. """ -import json from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import ( @@ -23,7 +20,6 @@ AutoCodeInterpreterToolParam, FunctionTool, ) -from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam class TestAgentCodeInterpreterAndFunction(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py index f4996551357b..7bcdc9c57b42 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_code_interpreter.py @@ -5,8 +5,6 @@ # ------------------------------------ # cSpell:disable -import pytest - """ Multi-Tool Tests: File Search + Code Interpreter diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py index 9adb1329774a..d55fdca08792 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_and_function.py @@ -5,8 +5,6 @@ # ------------------------------------ # cSpell:disable -import pytest - """ Multi-Tool Tests: File Search + Function Tool @@ -18,8 +16,8 @@ from io import BytesIO from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport -from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool, FunctionTool from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool, FunctionTool class TestAgentFileSearchAndFunction(TestBase): @@ -362,7 +360,7 @@ def calculate_sum(numbers): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_multi_turn_search_and_save_workflow(self, **kwargs): + def test_multi_turn_search_and_save_workflow(self, **kwargs): # pylint: disable=too-many-statements,too-many-locals """ Test multi-turn workflow: search documents, ask follow-ups, save findings. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py index 648a1ed65519..d362946f24b0 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_agent_file_search_code_interpreter_function.py @@ -4,9 +4,6 @@ # Licensed under the MIT License. # ------------------------------------ # cSpell:disable - -import pytest - """ Multi-Tool Tests: File Search + Code Interpreter + Function Tool @@ -14,7 +11,6 @@ All tests use the same 3-tool combination but different inputs and workflows. """ -import json from io import BytesIO from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport @@ -25,7 +21,6 @@ AutoCodeInterpreterToolParam, FunctionTool, ) -from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam class TestAgentFileSearchCodeInterpreterFunction(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py index f4a20b8f21ad..2baea19a4160 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/multitool/test_multitool_with_conversations.py @@ -2,9 +2,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ - -import pytest - """ Test agents using multiple tools within conversations. @@ -14,6 +11,7 @@ import json from io import BytesIO +from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import ( @@ -21,14 +19,13 @@ FileSearchTool, PromptAgentDefinition, ) -from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam class TestMultiToolWithConversations(TestBase): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_file_search_and_function_with_conversation(self, **kwargs): + def test_file_search_and_function_with_conversation(self, **kwargs): # pylint: disable=too-many-statements """ Test using multiple tools (FileSearch + Function) within one conversation. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py index e16a8e0e5722..70ff70113560 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_ai_search.py @@ -52,7 +52,7 @@ class TestAgentAISearch(TestBase): condition=(not is_live_and_not_recording()), reason="Skipped because we cannot record network calls with OpenAI client", ) - def test_agent_ai_search_question_answering(self, **kwargs): + def test_agent_ai_search_question_answering(self, **kwargs): # pylint: disable=too-many-statements """ Test agent with Azure AI Search capabilities for question answering. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py index 190183663823..1b860ff45792 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_bing_grounding.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -100,7 +100,7 @@ def test_agent_bing_grounding(self, **kwargs): elif event.type == "response.output_text.delta": print(f"Delta: {event.delta}") elif event.type == "response.text.done": - print(f"Follow-up response done!") + print("Follow-up response done!") elif event.type == "response.output_item.done": if event.item.type == "message": item = event.item @@ -112,7 +112,7 @@ def test_agent_bing_grounding(self, **kwargs): print(f"URL Citation: {annotation.url}") url_citations.append(annotation.url) elif event.type == "response.completed": - print(f"Follow-up completed!") + print("Follow-up completed!") print(f"Full response: {event.response.output_text}") output_text = event.response.output_text diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py index 1d49bb0bbb6b..3cffa5ef23a1 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py index 50a4b778cb07..9147c068adc1 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_code_interpreter_async.py @@ -5,7 +5,6 @@ # ------------------------------------ # cSpell:disable -import pytest from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py index 9c733529ac42..011c580ae17f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search.py @@ -6,8 +6,8 @@ # cSpell:disable import os -import pytest from io import BytesIO +import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool @@ -156,7 +156,7 @@ def test_agent_file_search_unsupported_file_type(self, **kwargs): # Attempt to upload unsupported file type print("\nAttempting to upload CSV file (unsupported format)...") try: - file = openai_client.vector_stores.files.upload_and_poll( + _ = openai_client.vector_stores.files.upload_and_poll( vector_store_id=vector_store.id, file=csv_file, ) @@ -164,7 +164,7 @@ def test_agent_file_search_unsupported_file_type(self, **kwargs): openai_client.vector_stores.delete(vector_store.id) pytest.fail("Expected BadRequestError for CSV file upload, but upload succeeded") - except Exception as e: + except Exception as e: # pylint: disable=broad-exception-caught error_message = str(e) print(f"\n✓ Upload correctly rejected with error: {error_message[:200]}...") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py index 604fbd5323fe..1bda2739a3e4 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_async.py @@ -6,7 +6,6 @@ # cSpell:disable import os -import pytest from io import BytesIO from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py index 72079587549e..1ff21ef6c57d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream.py @@ -6,7 +6,6 @@ # cSpell:disable import os -import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py index 42e1348a3521..c0357be6376d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_file_search_stream_async.py @@ -6,7 +6,6 @@ # cSpell:disable import os -import pytest from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py index 05ebd8ddcad0..fe8349055685 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool.py @@ -6,11 +6,10 @@ # cSpell:disable import json -import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport -from azure.ai.projects.models import PromptAgentDefinition, FunctionTool from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, FunctionTool class TestAgentFunctionTool(TestBase): @@ -162,7 +161,7 @@ def test_agent_function_tool(self, **kwargs): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_agent_function_tool_multi_turn_with_multiple_calls(self, **kwargs): + def test_agent_function_tool_multi_turn_with_multiple_calls(self, **kwargs): # pylint: disable=too-many-statements """ Test multi-turn conversation where agent calls functions multiple times. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py index 2344c5a9d498..660ac4333b38 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_function_tool_async.py @@ -6,12 +6,11 @@ # cSpell:disable import json -import pytest from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport -from azure.ai.projects.models import PromptAgentDefinition, FunctionTool from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, FunctionTool class TestAgentFunctionToolAsync(TestBase): @@ -150,7 +149,9 @@ async def test_agent_function_tool_async(self, **kwargs): @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_agent_function_tool_multi_turn_with_multiple_calls_async(self, **kwargs): + async def test_agent_function_tool_multi_turn_with_multiple_calls_async( + self, **kwargs + ): # pylint: disable=too-many-statements """ Test multi-turn conversation where agent calls functions multiple times (async version). diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py index 7dea648d735d..dfec50f05ae5 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression,broad-exception-caught # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py index 68b91adb44d4..00f8edc2d866 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_image_generation_async.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression,broad-exception-caught # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py index 109e1b93f775..2067a94e11b0 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp.py @@ -9,8 +9,8 @@ from test_base import TestBase, servicePreparer from devtools_testutils import is_live_and_not_recording from devtools_testutils import recorded_by_proxy, RecordedTransport -from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool class TestAgentMCP(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py index b9e1dd43c7e1..471f1b4809f1 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_mcp_async.py @@ -5,12 +5,11 @@ # ------------------------------------ # cSpell:disable -import pytest from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport -from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam +from azure.ai.projects.models import PromptAgentDefinition, MCPTool, Tool class TestAgentMCPAsync(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py index 56edc1879033..9f2fc80b6301 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search.py @@ -1,13 +1,13 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression,broad-exception-caught # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ # cSpell:disable -import pytest import time from typing import Final +import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport, is_live, is_live_and_not_recording from azure.core.exceptions import ResourceNotFoundError @@ -26,7 +26,7 @@ class TestAgentMemorySearch(TestBase): ) @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_agent_memory_search(self, **kwargs): + def test_agent_memory_search(self, **kwargs): # pylint: disable=too-many-statements """ Test agent with Memory Search tool for contextual memory retrieval. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py index f830aac14fca..bea2d6053eb3 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_memory_search_async.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression,broad-exception-caught # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -6,8 +6,8 @@ # cSpell:disable import asyncio -import pytest from typing import Final +import pytest from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport, is_live, is_live_and_not_recording @@ -27,7 +27,7 @@ class TestAgentMemorySearchAsync(TestBase): ) @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_agent_memory_search_async(self, **kwargs): + async def test_agent_memory_search_async(self, **kwargs): # pylint: disable=too-many-statements model = kwargs.get("foundry_model_name") chat_model = kwargs.get("memory_store_chat_model_deployment_name") diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py index 0bb1aa3cdf33..874c4414c427 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi.py @@ -65,7 +65,7 @@ def test_agent_openapi(self, **kwargs): assert os.path.exists(weather_asset_file_path), f"OpenAPI spec file not found at: {weather_asset_file_path}" print(f"Using OpenAPI spec file: {weather_asset_file_path}") - with open(weather_asset_file_path, "r") as f: + with open(weather_asset_file_path, "r", encoding="utf-8") as f: openapi_weather = jsonref.loads(f.read()) # Create OpenAPI tool diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py index dc363934a816..6ef72d8b3338 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_openapi_async.py @@ -45,7 +45,7 @@ async def test_agent_openapi_async(self, **kwargs): assert os.path.exists(weather_asset_file_path), f"OpenAPI spec file not found at: {weather_asset_file_path}" print(f"Using OpenAPI spec file: {weather_asset_file_path}") - with open(weather_asset_file_path, "r") as f: + with open(weather_asset_file_path, "r", encoding="utf-8") as f: openapi_weather = jsonref.loads(f.read()) # Create OpenAPI tool diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py index 1f4e5e78ef65..6e3cafa96e6e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_tools_with_conversations.py @@ -12,9 +12,9 @@ """ import json -import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport +from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam from azure.ai.projects.models import ( FunctionTool, FileSearchTool, @@ -22,14 +22,13 @@ AutoCodeInterpreterToolParam, PromptAgentDefinition, ) -from openai.types.responses.response_input_param import FunctionCallOutput, ResponseInputParam class TestAgentToolsWithConversations(TestBase): @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_function_tool_with_conversation(self, **kwargs): + def test_function_tool_with_conversation(self, **kwargs): # pylint: disable=too-many-statements """ Test using FunctionTool within a conversation. diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py index 084785bc53f3..1f6634a8640f 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search.py @@ -5,7 +5,6 @@ # ------------------------------------ # cSpell:disable -import pytest from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport from azure.ai.projects.models import PromptAgentDefinition, WebSearchPreviewTool, ApproximateLocation diff --git a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py index 7aac8aef6977..b73bc967d9db 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/tools/test_agent_web_search_async.py @@ -5,7 +5,6 @@ # ------------------------------------ # cSpell:disable -import pytest from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport diff --git a/sdk/ai/azure-ai-projects/tests/connections/test_connections.py b/sdk/ai/azure-ai-projects/tests/connections/test_connections.py index f2b6423e1cd0..4b44ff719a3b 100644 --- a/sdk/ai/azure-ai-projects/tests/connections/test_connections.py +++ b/sdk/ai/azure-ai-projects/tests/connections/test_connections.py @@ -4,12 +4,11 @@ # Licensed under the MIT License. # ------------------------------------ -import pytest +from test_base import TestBase, servicePreparer +from devtools_testutils import recorded_by_proxy from azure.ai.projects.models import ConnectionType, CredentialType, CustomCredential import azure.ai.projects.models as _models from azure.ai.projects._utils.model_base import _deserialize -from test_base import TestBase, servicePreparer -from devtools_testutils import recorded_by_proxy class TestConnections(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/connections/test_connections_async.py b/sdk/ai/azure-ai-projects/tests/connections/test_connections_async.py index 137381284c3b..6394099ad7fc 100644 --- a/sdk/ai/azure-ai-projects/tests/connections/test_connections_async.py +++ b/sdk/ai/azure-ai-projects/tests/connections/test_connections_async.py @@ -3,11 +3,9 @@ # Licensed under the MIT License. # ------------------------------------ -import pytest -from azure.ai.projects.aio import AIProjectClient -from azure.ai.projects.models import ConnectionType from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async +from azure.ai.projects.models import ConnectionType class TestConnectionsAsync(TestBase): diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py index 634076e78aa6..2293587f2bdf 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets.py @@ -5,12 +5,11 @@ # ------------------------------------ import os import re -import pytest +from test_base import TestBase, servicePreparer +from devtools_testutils import recorded_by_proxy, is_live, add_general_regex_sanitizer from azure.ai.projects import AIProjectClient from azure.ai.projects.models import DatasetVersion, DatasetType from azure.ai.projects.models._enums import ConnectionType -from test_base import TestBase, servicePreparer -from devtools_testutils import recorded_by_proxy, is_live, add_general_regex_sanitizer from azure.core.exceptions import HttpResponseError # Construct the paths to the data folder and data file used in this test @@ -37,7 +36,7 @@ def test_datasets_upload_file(self, **kwargs): with self.create_client(**kwargs) as project_client: - print(f"Get the default Azure Storage connection to use for uploading files.") + print("Get the default Azure Storage connection to use for uploading files.") connection_name = project_client.connections.get_default(ConnectionType.AZURE_STORAGE_ACCOUNT).name print( f"[test_datasets_upload_file] Upload a single file and create a new Dataset `{dataset_name}`, version `{dataset_version}`, to reference the file." @@ -90,6 +89,7 @@ def test_datasets_upload_file(self, **kwargs): print(dataset_credential) TestBase.validate_dataset_credential(dataset_credential) + # pylint: disable=pointless-string-statement """ print("[test_datasets_upload_file] List latest versions of all Datasets:") empty = True @@ -152,7 +152,7 @@ def test_datasets_upload_folder(self, **kwargs): credential=self.get_credential(AIProjectClient, is_async=False), ) as project_client: - print(f"Get the default Azure Storage connection to use for uploading files.") + print("Get the default Azure Storage connection to use for uploading files.") connection_name = project_client.connections.get_default(ConnectionType.AZURE_STORAGE_ACCOUNT).name print( f"[test_datasets_upload_folder] Upload files in a folder (including sub-folders) and create a new version `{dataset_version}` in the same Dataset, to reference the files." diff --git a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py index 3e53e4b3d9ed..eedaa1da5424 100644 --- a/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py +++ b/sdk/ai/azure-ai-projects/tests/datasets/test_datasets_async.py @@ -5,13 +5,12 @@ # ------------------------------------ import os import re -import pytest -from azure.ai.projects.aio import AIProjectClient -from azure.ai.projects.models import DatasetVersion, DatasetType -from azure.ai.projects.models._enums import ConnectionType from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import is_live, add_general_regex_sanitizer +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import DatasetVersion, DatasetType +from azure.ai.projects.models._enums import ConnectionType from azure.core.exceptions import HttpResponseError # Construct the paths to the data folder and data file used in this test @@ -38,7 +37,7 @@ async def test_datasets_upload_file(self, **kwargs): async with self.create_async_client(**kwargs) as project_client: - print(f"Get the default Azure Storage connection to use for uploading files.") + print("Get the default Azure Storage connection to use for uploading files.") connection_name = (await project_client.connections.get_default(ConnectionType.AZURE_STORAGE_ACCOUNT)).name print( f"[test_datasets_upload_file] Upload a single file and create a new Dataset `{dataset_name}`, version `{dataset_version}`, to reference the file." @@ -91,6 +90,7 @@ async def test_datasets_upload_file(self, **kwargs): print(dataset_credential) TestBase.validate_dataset_credential(dataset_credential) + # pylint: disable=pointless-string-statement """ print("[test_datasets_upload_file] List latest versions of all Datasets:") empty = True @@ -153,7 +153,7 @@ async def test_datasets_upload_folder_async(self, **kwargs): credential=self.get_credential(AIProjectClient, is_async=True), ) as project_client: - print(f"Get the default Azure Storage connection to use for uploading files.") + print("Get the default Azure Storage connection to use for uploading files.") connection_name = (await project_client.connections.get_default(ConnectionType.AZURE_STORAGE_ACCOUNT)).name print( f"[test_datasets_upload_folder] Upload files in a folder (including sub-folders) and create a new version `{dataset_version}` in the same Dataset, to reference the files." diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py index c2345115eb92..ee91043c1f75 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments.py @@ -3,8 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ -import pytest -from azure.ai.projects import AIProjectClient from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy diff --git a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py index b278909621e4..24e6630f3e24 100644 --- a/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py +++ b/sdk/ai/azure-ai-projects/tests/deployments/test_deployments_async.py @@ -3,8 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ -import pytest -from azure.ai.projects.aio import AIProjectClient from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async diff --git a/sdk/ai/azure-ai-projects/tests/files/test_files.py b/sdk/ai/azure-ai-projects/tests/files/test_files.py index f934ce955547..56e1125e8b07 100644 --- a/sdk/ai/azure-ai-projects/tests/files/test_files.py +++ b/sdk/ai/azure-ai-projects/tests/files/test_files.py @@ -3,8 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ -import re -import pytest from pathlib import Path from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, RecordedTransport diff --git a/sdk/ai/azure-ai-projects/tests/files/test_files_async.py b/sdk/ai/azure-ai-projects/tests/files/test_files_async.py index cc85b778e1a5..968dc7b04d50 100644 --- a/sdk/ai/azure-ai-projects/tests/files/test_files_async.py +++ b/sdk/ai/azure-ai-projects/tests/files/test_files_async.py @@ -3,8 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ -import re -import pytest from pathlib import Path from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index cdf1b9f4ceda..1d5b61e920be 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -5,9 +5,9 @@ # ------------------------------------ import os -import pytest import time from pathlib import Path +import pytest from test_base import ( TestBase, servicePreparer, @@ -318,7 +318,7 @@ def _test_rft_create_job_helper(self, model_type, training_type, **kwargs): self._cleanup_test_file(openai_client, train_file.id) self._cleanup_test_file(openai_client, validation_file.id) - def _extract_account_name_from_endpoint(self, project_endpoint, test_prefix): + def _extract_account_name_from_endpoint(self, project_endpoint: str) -> str: endpoint_clean = project_endpoint.replace("https://", "").replace("http://", "") if ".services.ai.azure.com" not in endpoint_clean: raise ValueError( @@ -327,7 +327,13 @@ def _extract_account_name_from_endpoint(self, project_endpoint, test_prefix): return endpoint_clean.split(".services.ai.azure.com")[0] def _test_deploy_and_infer_helper( - self, completed_job_id, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + self, + completed_job_id: str, + deployment_format: str, + deployment_capacity: int, + test_prefix: str, + inference_content: str, + **kwargs, ): if not completed_job_id: pytest.skip(f"completed_job_id parameter not set - skipping {test_prefix} deploy and infer test") @@ -341,7 +347,7 @@ def _test_deploy_and_infer_helper( f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, foundry_project_endpoint) - skipping {test_prefix} deploy and infer test" ) - account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) + account_name = self._extract_account_name_from_endpoint(project_endpoint) print(f"[{test_prefix}] Account name: {account_name}") with self.create_client(**kwargs) as project_client: diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index bd4e29481dbe..1effc1f06bdb 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -5,9 +5,9 @@ # ------------------------------------ import os -import pytest import asyncio from pathlib import Path +import pytest from test_base import ( TestBase, servicePreparer, @@ -329,7 +329,7 @@ async def _test_rft_create_job_helper_async(self, model_type, training_type, **k await self._cleanup_test_file_async(openai_client, train_file.id) await self._cleanup_test_file_async(openai_client, validation_file.id) - def _extract_account_name_from_endpoint(self, project_endpoint, test_prefix): + def _extract_account_name_from_endpoint(self, project_endpoint: str) -> str: endpoint_clean = project_endpoint.replace("https://", "").replace("http://", "") if ".services.ai.azure.com" not in endpoint_clean: raise ValueError( @@ -338,7 +338,13 @@ def _extract_account_name_from_endpoint(self, project_endpoint, test_prefix): return endpoint_clean.split(".services.ai.azure.com")[0] async def _test_deploy_and_infer_helper_async( - self, completed_job_id, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + self, + completed_job_id: str, + deployment_format: str, + deployment_capacity: int, + test_prefix: str, + inference_content: str, + **kwargs, ): if not completed_job_id: pytest.skip(f"completed_job_id parameter not set - skipping {test_prefix} deploy and infer test") @@ -352,7 +358,7 @@ async def _test_deploy_and_infer_helper_async( f"Missing required environment variables for deployment (azure_subscription_id, azure_resource_group, foundry_project_endpoint) - skipping {test_prefix} deploy and infer test" ) - account_name = self._extract_account_name_from_endpoint(project_endpoint, test_prefix) + account_name = self._extract_account_name_from_endpoint(project_endpoint) print(f"[{test_prefix}] Account name: {account_name}") project_client = self.create_async_client(**kwargs) diff --git a/sdk/ai/azure-ai-projects/tests/indexes/test_indexes.py b/sdk/ai/azure-ai-projects/tests/indexes/test_indexes.py index b09f404b0a08..db48230296c8 100644 --- a/sdk/ai/azure-ai-projects/tests/indexes/test_indexes.py +++ b/sdk/ai/azure-ai-projects/tests/indexes/test_indexes.py @@ -5,10 +5,9 @@ # ------------------------------------ import pytest -from azure.ai.projects import AIProjectClient -from azure.ai.projects.models import AzureAISearchIndex, IndexType from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy +from azure.ai.projects.models import AzureAISearchIndex, IndexType @pytest.mark.skip(reason="Backend throw 400 on index list api") diff --git a/sdk/ai/azure-ai-projects/tests/indexes/test_indexes_async.py b/sdk/ai/azure-ai-projects/tests/indexes/test_indexes_async.py index a42af11fb517..69bd4c268057 100644 --- a/sdk/ai/azure-ai-projects/tests/indexes/test_indexes_async.py +++ b/sdk/ai/azure-ai-projects/tests/indexes/test_indexes_async.py @@ -5,10 +5,9 @@ # ------------------------------------ import pytest -from azure.ai.projects.aio import AIProjectClient -from azure.ai.projects.models import AzureAISearchIndex, IndexType from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async +from azure.ai.projects.models import AzureAISearchIndex, IndexType @pytest.mark.skip(reason="Backend throw 400 on index list api") diff --git a/sdk/ai/azure-ai-projects/tests/redteams/test_redteams.py b/sdk/ai/azure-ai-projects/tests/redteams/test_redteams.py index 425bc89e98ee..3285c1afb474 100644 --- a/sdk/ai/azure-ai-projects/tests/redteams/test_redteams.py +++ b/sdk/ai/azure-ai-projects/tests/redteams/test_redteams.py @@ -4,15 +4,14 @@ # ------------------------------------ import pytest -from azure.ai.projects import AIProjectClient +from test_base import TestBase, servicePreparer +from devtools_testutils import recorded_by_proxy from azure.ai.projects.models import ( RedTeam, AzureOpenAIModelConfiguration, AttackStrategy, RiskCategory, ) -from test_base import TestBase, servicePreparer -from devtools_testutils import recorded_by_proxy @pytest.mark.skip( diff --git a/sdk/ai/azure-ai-projects/tests/redteams/test_redteams_async.py b/sdk/ai/azure-ai-projects/tests/redteams/test_redteams_async.py index 65dc52989320..3fda37b15180 100644 --- a/sdk/ai/azure-ai-projects/tests/redteams/test_redteams_async.py +++ b/sdk/ai/azure-ai-projects/tests/redteams/test_redteams_async.py @@ -4,15 +4,14 @@ # ------------------------------------ import pytest -from azure.ai.projects.aio import AIProjectClient +from test_base import TestBase, servicePreparer +from devtools_testutils.aio import recorded_by_proxy_async from azure.ai.projects.models import ( RedTeam, AzureOpenAIModelConfiguration, AttackStrategy, RiskCategory, ) -from test_base import TestBase, servicePreparer -from devtools_testutils.aio import recorded_by_proxy_async @pytest.mark.skip( diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py b/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py index 2aacdfe74707..782c5024f43a 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides.py @@ -9,9 +9,9 @@ """ import os +from typing import Any import pytest import httpx -from typing import Any from azure.core.credentials import TokenCredential from azure.ai.projects import AIProjectClient diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py b/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py index 0abf63f41963..de8c484fd9f6 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_openai_client_overrides_async.py @@ -9,9 +9,9 @@ """ import os +from typing import Any import pytest import httpx -from typing import Any from azure.core.credentials_async import AsyncTokenCredential from azure.ai.projects.aio import AIProjectClient diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py index 9ef5758e06f3..9ea48ffe05e7 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses.py @@ -5,11 +5,11 @@ # ------------------------------------ # cSpell:disable +from typing import Any, Dict, Optional import pytest import httpx from devtools_testutils import recorded_by_proxy, RecordedTransport from test_base import TestBase, servicePreparer -from typing import Any, Dict, Optional from openai import OpenAI from azure.core.credentials import TokenCredential from azure.ai.projects import AIProjectClient @@ -105,12 +105,12 @@ def test_responses(self, **kwargs): ) def test_user_agent_patching_via_response_create( self, project_ua, openai_default_header, expected_ua, patch_openai - ): + ): # pylint: disable=redefined-outer-name,unused-argument client = _build_client(project_ua, openai_default_header) calls = [] - def fake_send(request: httpx.Request, *args: Any, **kwargs: Any): + def fake_send(request: httpx.Request, *_args: Any, **kwargs: Any): # Capture headers that would be sent over the wire. calls.append(dict(request.headers)) return httpx.Response( diff --git a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py index 67eb01609039..e816e6741737 100644 --- a/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py +++ b/sdk/ai/azure-ai-projects/tests/responses/test_responses_async.py @@ -5,16 +5,16 @@ # ------------------------------------ # cSpell:disable +from typing import Any, Dict, Optional import pytest import httpx -from typing import Any, Dict, Optional from openai import AsyncOpenAI -from azure.core.credentials import AccessToken -from azure.core.credentials_async import AsyncTokenCredential -from azure.ai.projects.aio import AIProjectClient from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import RecordedTransport +from azure.core.credentials import AccessToken +from azure.core.credentials_async import AsyncTokenCredential +from azure.ai.projects.aio import AIProjectClient BASE_OPENAI_UA = AsyncOpenAI(api_key="dummy").user_agent @@ -99,7 +99,7 @@ async def test_user_agent_patching_via_response_create(self, project_ua, openai_ calls = [] - async def fake_send(request: httpx.Request, *args: Any, **kwargs: Any): + async def fake_send(request: httpx.Request, *_args: Any, **kwargs: Any): # Capture headers that would be sent over the wire. calls.append(dict(request.headers)) return httpx.Response( diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 494a47c1a3a2..5416b3269e7b 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -73,6 +73,7 @@ def test_agent_tools_samples(self, sample_path: str, **kwargs) -> None: samples_to_skip=[ "sample_memory_advanced.py", "sample_memory_basic.py", + "sample_memory_crud.py", # Sample works fine. But AI thinks something is wrong. ], ), ) @@ -94,7 +95,7 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: "sample_path", get_sample_paths( "agents", - samples_to_skip=[""], + samples_to_skip=["sample_workflow_multi_agent.py"], # I see in sample spew: "Event 10 type 'response.failed'" with error message in payload "The specified agent was not found. Please verify that the agent name and version are correct". ), ) @servicePreparer() diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index a1d3a68a0b9b..18336f6122a9 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -170,6 +170,7 @@ class TestSamplesEvaluations(AzureRecordedTestCase): "sample_evaluations_builtin_with_csv.py", # Requires CSV file upload prerequisite "sample_synthetic_data_agent_evaluation.py", # Synthetic data gen is long-running preview feature "sample_synthetic_data_model_evaluation.py", # Synthetic data gen is long-running preview feature + "sample_eval_catalog_prompt_based_evaluators.py", # For some reason fails with 500 (Internal server error) ], ), ) diff --git a/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry.py b/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry.py index 9ade1692ae0a..7cb0bd587649 100644 --- a/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry.py +++ b/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry.py @@ -3,8 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ -import pytest -from azure.ai.projects import AIProjectClient from test_base import TestBase, servicePreparer from devtools_testutils import recorded_by_proxy, is_live diff --git a/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry_async.py b/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry_async.py index d0aee2d61e4b..378108b22e84 100644 --- a/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry_async.py +++ b/sdk/ai/azure-ai-projects/tests/telemetry/test_telemetry_async.py @@ -3,8 +3,6 @@ # Licensed under the MIT License. # ------------------------------------ -import pytest -from azure.ai.projects.aio import AIProjectClient from test_base import TestBase, servicePreparer from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import is_live diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index 74fa089c10c2..af3d3e3f30a3 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -10,13 +10,14 @@ import os import tempfile from typing import Optional, Any, Dict, Final, IO, Union, overload, Literal, TextIO, BinaryIO +from openai.types.responses import Response +from openai.types.conversations import ConversationItem +from devtools_testutils import AzureRecordedTestCase, EnvironmentVariableLoader from azure.ai.projects.models import ( - ApiKeyCredentials, AzureAISearchIndex, Connection, ConnectionType, CredentialType, - CustomCredential, DatasetCredential, DatasetType, DatasetVersion, @@ -26,11 +27,8 @@ IndexType, ModelDeployment, ) -from openai.types.responses import Response -from openai.types.conversations import ConversationItem from azure.ai.projects.models._models import AgentDetails, AgentVersionDetails -from devtools_testutils import AzureRecordedTestCase, EnvironmentVariableLoader -from azure.ai.projects import AIProjectClient as AIProjectClient +from azure.ai.projects import AIProjectClient from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient # Store reference to built-in open before any mocking occurs @@ -157,11 +155,10 @@ def patched_open_crlf_to_lf(*args, **kwargs): if args: # File path was passed as positional arg return _BUILTIN_OPEN(temp_path, *args[1:], **kwargs) - else: - # File path was passed as keyword arg - kwargs = kwargs.copy() - kwargs["file"] = temp_path - return _BUILTIN_OPEN(**kwargs) + # File path was passed as keyword arg + kwargs = kwargs.copy() + kwargs["file"] = temp_path + return _BUILTIN_OPEN(**kwargs) return _BUILTIN_OPEN(*args, **kwargs) @@ -187,7 +184,7 @@ class TestBase(AzureRecordedTestCase): } test_indexes_params = { - "index_name": f"test-index-name", + "index_name": "test-index-name", "index_version": "1", "ai_search_connection_name": "my-ai-search-connection", "ai_search_index_name": "my-ai-search-index", @@ -438,7 +435,7 @@ def validate_deployment( expected_model_deployment_name: Optional[str] = None, expected_model_publisher: Optional[str] = None, ): - assert type(deployment) == ModelDeployment + assert isinstance(deployment, ModelDeployment) assert deployment.type == DeploymentType.MODEL_DEPLOYMENT assert deployment.model_version is not None # Comment out the below, since I see that `Cohere-embed-v3-english` has an empty capabilities dict. @@ -465,7 +462,7 @@ def validate_index( TestBase.assert_equal_or_not_none(index.version, expected_index_version) if expected_index_type == IndexType.AZURE_SEARCH: - assert type(index) == AzureAISearchIndex + assert isinstance(index, AzureAISearchIndex) assert index.type == IndexType.AZURE_SEARCH TestBase.assert_equal_or_not_none(index.connection_name, expected_ai_search_connection_name) TestBase.assert_equal_or_not_none(index.index_name, expected_ai_search_index_name) @@ -485,7 +482,7 @@ def validate_dataset( if expected_dataset_type: assert dataset.type == expected_dataset_type else: - assert dataset.type == DatasetType.URI_FILE or dataset.type == DatasetType.URI_FOLDER + assert dataset.type in (DatasetType.URI_FILE, DatasetType.URI_FOLDER) TestBase.assert_equal_or_not_none(dataset.name, expected_dataset_name) TestBase.assert_equal_or_not_none(dataset.version, expected_dataset_version) @@ -632,7 +629,7 @@ def validate_fine_tuning_job( TestBase.assert_equal_or_not_none(job_obj.status, expected_status) def _request_callback(self, pipeline_request) -> None: - self.pipeline_request = pipeline_request + self.pipeline_request = pipeline_request # pylint: disable=attribute-defined-outside-init @staticmethod def _are_json_equal(json_str1: str, json_str2: str) -> bool: From cb4ef7b39a5fe13b785550e49a9b76a15dabeb48 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Thu, 12 Mar 2026 08:33:52 -0700 Subject: [PATCH 13/36] Re-emit from latest TypeSpec in branch `feature/foundry-staging` (#45659) --- sdk/ai/azure-ai-projects/README.md | 4 +- .../azure-ai-projects/apiview-properties.json | 9 +- .../ai/projects/aio/operations/_operations.py | 6902 ++++++++-------- .../azure/ai/projects/models/__init__.py | 10 + .../azure/ai/projects/models/_enums.py | 13 + .../azure/ai/projects/models/_models.py | 448 +- .../ai/projects/operations/_operations.py | 7170 +++++++++-------- .../azure-ai-projects/post-emitter-fixes.cmd | 2 +- .../agents/tools/sample_agent_file_search.py | 4 +- .../sample_agent_file_search_in_stream.py | 4 +- ...ample_agent_file_search_in_stream_async.py | 4 +- .../sample_synthetic_data_model_evaluation.py | 2 +- ...t_responses_instrumentor_workflow_async.py | 2 +- .../tests/samples/test_samples.py | 6 +- .../tests/samples/test_samples_evaluations.py | 2 +- 15 files changed, 7590 insertions(+), 6992 deletions(-) diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 2b30726f33aa..93d3cd788fb8 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -276,9 +276,7 @@ asset_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../as # Upload file to vector store with open(asset_file_path, "rb") as f: - file = openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=f - ) + file = openai_client.vector_stores.files.upload_and_poll(vector_store_id=vector_store.id, file=f) print(f"File uploaded to vector store (id: {file.id})") tool = FileSearchTool(vector_store_ids=[vector_store.id]) diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json index e57c56c7a12e..67119b7f250f 100644 --- a/sdk/ai/azure-ai-projects/apiview-properties.json +++ b/sdk/ai/azure-ai-projects/apiview-properties.json @@ -84,6 +84,7 @@ "azure.ai.projects.models.DeleteAgentResponse": "Azure.AI.Projects.DeleteAgentResponse", "azure.ai.projects.models.DeleteAgentVersionResponse": "Azure.AI.Projects.DeleteAgentVersionResponse", "azure.ai.projects.models.DeleteMemoryStoreResult": "Azure.AI.Projects.DeleteMemoryStoreResponse", + "azure.ai.projects.models.DeleteToolsetResponse": "Azure.AI.Projects.DeleteToolsetResponse", "azure.ai.projects.models.Deployment": "Azure.AI.Projects.Deployment", "azure.ai.projects.models.EmbeddingConfiguration": "Azure.AI.Projects.EmbeddingConfiguration", "azure.ai.projects.models.EntraIDCredentials": "Azure.AI.Projects.EntraIDCredentials", @@ -205,6 +206,7 @@ "azure.ai.projects.models.ToolChoiceWebSearchPreview20250311": "OpenAI.ToolChoiceWebSearchPreview20250311", "azure.ai.projects.models.ToolDescription": "Azure.AI.Projects.ToolDescription", "azure.ai.projects.models.ToolProjectConnection": "Azure.AI.Projects.ToolProjectConnection", + "azure.ai.projects.models.ToolsetObject": "Azure.AI.Projects.ToolsetObject", "azure.ai.projects.models.UserProfileMemoryItem": "Azure.AI.Projects.UserProfileMemoryItem", "azure.ai.projects.models.WebSearchApproximateLocation": "OpenAI.WebSearchApproximateLocation", "azure.ai.projects.models.WebSearchConfiguration": "Azure.AI.Projects.WebSearchConfiguration", @@ -213,6 +215,8 @@ "azure.ai.projects.models.WebSearchToolFilters": "OpenAI.WebSearchToolFilters", "azure.ai.projects.models.WeeklyRecurrenceSchedule": "Azure.AI.Projects.WeeklyRecurrenceSchedule", "azure.ai.projects.models.WorkflowAgentDefinition": "Azure.AI.Projects.WorkflowAgentDefinition", + "azure.ai.projects.models.WorkIQPreviewTool": "Azure.AI.Projects.WorkIQPreviewTool", + "azure.ai.projects.models.WorkIQPreviewToolParameters": "Azure.AI.Projects.WorkIQPreviewToolParameters", "azure.ai.projects.models.EvaluationTaxonomyInputType": "Azure.AI.Projects.EvaluationTaxonomyInputType", "azure.ai.projects.models.RiskCategory": "Azure.AI.Projects.RiskCategory", "azure.ai.projects.models.FoundryFeaturesOptInKeys": "Azure.AI.Projects.FoundryFeaturesOptInKeys", @@ -237,8 +241,7 @@ "azure.ai.projects.models.RecurrenceType": "Azure.AI.Projects.RecurrenceType", "azure.ai.projects.models.DayOfWeek": "Azure.AI.Projects.DayOfWeek", "azure.ai.projects.models.ScheduleTaskType": "Azure.AI.Projects.ScheduleTaskType", - "azure.ai.projects.models.AgentObjectType": "Azure.AI.Projects.AgentObjectType", - "azure.ai.projects.models.AgentKind": "Azure.AI.Projects.AgentKind", + "azure.ai.projects.models.ToolsetObjectType": "Azure.AI.Projects.ToolsetObjectType", "azure.ai.projects.models.ToolType": "OpenAI.ToolType", "azure.ai.projects.models.AzureAISearchQueryType": "Azure.AI.Projects.AzureAISearchQueryType", "azure.ai.projects.models.ContainerMemoryLimit": "OpenAI.ContainerMemoryLimit", @@ -253,6 +256,8 @@ "azure.ai.projects.models.FunctionShellToolParamEnvironmentType": "OpenAI.FunctionShellToolParamEnvironmentType", "azure.ai.projects.models.ContainerSkillType": "OpenAI.ContainerSkillType", "azure.ai.projects.models.SearchContextSize": "OpenAI.SearchContextSize", + "azure.ai.projects.models.AgentObjectType": "Azure.AI.Projects.AgentObjectType", + "azure.ai.projects.models.AgentKind": "Azure.AI.Projects.AgentKind", "azure.ai.projects.models.AgentProtocol": "Azure.AI.Projects.AgentProtocol", "azure.ai.projects.models.ToolChoiceParamType": "OpenAI.ToolChoiceParamType", "azure.ai.projects.models.TextResponseFormatConfigurationType": "OpenAI.TextResponseFormatConfigurationType", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 3499ba5338e5..5daa5483b071 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -38,8 +38,6 @@ from ...models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys from ...operations._operations import ( _get_agent_definition_opt_in_keys, - build_agents_create_agent_from_manifest_request, - build_agents_create_agent_request, build_agents_create_version_from_manifest_request, build_agents_create_version_request, build_agents_delete_request, @@ -48,8 +46,6 @@ build_agents_get_version_request, build_agents_list_request, build_agents_list_versions_request, - build_agents_update_agent_from_manifest_request, - build_agents_update_agent_request, build_beta_evaluation_taxonomies_create_request, build_beta_evaluation_taxonomies_delete_request, build_beta_evaluation_taxonomies_get_request, @@ -83,6 +79,11 @@ build_beta_schedules_get_run_request, build_beta_schedules_list_request, build_beta_schedules_list_runs_request, + build_beta_toolsets_create_request, + build_beta_toolsets_delete_request, + build_beta_toolsets_get_request, + build_beta_toolsets_list_request, + build_beta_toolsets_update_request, build_connections_get_request, build_connections_get_with_credentials_request, build_connections_list_request, @@ -116,7 +117,7 @@ _SERIALIZER.client_side_validation = False -class BetaOperations: +class BetaOperations: # pylint: disable=too-many-instance-attributes """ .. warning:: **DO NOT** instantiate this class directly. @@ -141,6 +142,7 @@ def __init__(self, *args, **kwargs) -> None: self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) self.red_teams = BetaRedTeamsOperations(self._client, self._config, self._serialize, self._deserialize) self.schedules = BetaSchedulesOperations(self._client, self._config, self._serialize, self._deserialize) + self.toolsets = BetaToolsetsOperations(self._client, self._config, self._serialize, self._deserialize) class AgentsOperations: @@ -225,65 +227,16 @@ async def get(self, agent_name: str, **kwargs: Any) -> _models.AgentDetails: return deserialized # type: ignore - @overload - async def _create_agent( - self, - *, - name: str, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - async def _create_agent( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - async def _create_agent( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @distributed_trace_async - async def _create_agent( - self, - body: Union[JSON, IO[bytes]] = _Unset, - *, - name: str = _Unset, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: - """Creates the agent. - - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :paramtype name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + async def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: + """Deletes an agent. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :param agent_name: The name of the agent to delete. Required. + :type agent_name: str + :return: DeleteAgentResponse. The DeleteAgentResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DeleteAgentResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -292,31 +245,14 @@ async def _create_agent( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) - - if body is _Unset: - if name is _Unset: - raise TypeError("missing required argument: name") - if definition is _Unset: - raise TypeError("missing required argument: definition") - body = {"definition": definition, "description": description, "metadata": metadata, "name": name} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.DeleteAgentResponse] = kwargs.pop("cls", None) - _request = build_agents_create_agent_request( - foundry_features=_foundry_features, - content_type=content_type, + _request = build_agents_delete_request( + agent_name=agent_name, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -349,15 +285,110 @@ async def _create_agent( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.DeleteAgentResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + @distributed_trace + def list( + self, + *, + kind: Optional[Union[str, _models.AgentKind]] = None, + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + before: Optional[str] = None, + **kwargs: Any + ) -> AsyncItemPaged["_models.AgentDetails"]: + """Returns the list of all agents. + + :keyword kind: Filter agents by kind. If not provided, all agents are returned. Known values + are: "prompt", "hosted", and "workflow". Default value is None. + :paramtype kind: str or ~azure.ai.projects.models.AgentKind + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the + default is 20. Default value is None. + :paramtype limit: int + :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for + ascending order and``desc`` + for descending order. Known values are: "asc" and "desc". Default value is None. + :paramtype order: str or ~azure.ai.projects.models.PageOrder + :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your + place in the list. + For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page of the list. + Default value is None. + :paramtype before: str + :return: An iterator like instance of AgentDetails + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.AgentDetails] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.AgentDetails]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(_continuation_token=None): + + _request = build_agents_list_request( + kind=kind, + limit=limit, + order=order, + after=_continuation_token, + before=before, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.AgentDetails], + deserialized.get("data", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("last_id") or None, AsyncList(list_of_elem) + + async def get_next(_continuation_token=None): + _request = prepare_request(_continuation_token) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + @overload - async def _update_agent( + async def create_version( self, agent_name: str, *, @@ -366,18 +397,84 @@ async def _update_agent( metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + @overload - async def _update_agent( + async def create_version( self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + @overload - async def _update_agent( + async def create_version( self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace_async - async def _update_agent( + async def create_version( self, agent_name: str, body: Union[JSON, IO[bytes]] = _Unset, @@ -386,11 +483,15 @@ async def _update_agent( metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: - """Updates the agent by adding a new version if there are any changes to the agent definition. If - no changes, returns the existing agent version. + ) -> _models.AgentVersionDetails: + """Create a new agent version. - :param agent_name: The name of the agent to retrieve. Required. + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. :type agent_name: str :param body: Is either a JSON type or a IO[bytes] type. Required. :type body: JSON or IO[bytes] @@ -406,8 +507,8 @@ async def _update_agent( :paramtype metadata: dict[str, str] :keyword description: A human-readable description of the agent. Default value is None. :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore @@ -423,7 +524,7 @@ async def _update_agent( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) if body is _Unset: if definition is _Unset: @@ -437,7 +538,7 @@ async def _update_agent( else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_agents_update_agent_request( + _request = build_agents_create_version_request( agent_name=agent_name, foundry_features=_foundry_features, content_type=content_type, @@ -475,7 +576,7 @@ async def _update_agent( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.AgentVersionDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -483,49 +584,117 @@ async def _update_agent( return deserialized # type: ignore @overload - async def _create_agent_from_manifest( + async def create_version_from_manifest( self, + agent_name: str, *, - name: str, manifest_id: str, parameter_values: dict[str, Any], content_type: str = "application/json", metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword manifest_id: The manifest ID to import the agent version from. Required. + :paramtype manifest_id: str + :keyword parameter_values: The inputs to the manifest that will result in a fully materialized + Agent. Required. + :paramtype parameter_values: dict[str, any] + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + @overload - async def _create_agent_from_manifest( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + async def create_version_from_manifest( + self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + @overload - async def _create_agent_from_manifest( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + async def create_version_from_manifest( + self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace_async - async def _create_agent_from_manifest( + async def create_version_from_manifest( self, + agent_name: str, body: Union[JSON, IO[bytes]] = _Unset, *, - name: str = _Unset, manifest_id: str = _Unset, parameter_values: dict[str, Any] = _Unset, metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: - """Creates an agent from a manifest. + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword name: The unique name that identifies the agent. Name can be used to + :param agent_name: The unique name that identifies the agent. Name can be used to retrieve/update/delete the agent. * Must start and end with alphanumeric characters, * Can contain hyphens in the middle * Must not exceed 63 characters. Required. - :paramtype name: str + :type agent_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] :keyword manifest_id: The manifest ID to import the agent version from. Required. :paramtype manifest_id: str :keyword parameter_values: The inputs to the manifest that will result in a fully materialized @@ -540,8 +709,8 @@ async def _create_agent_from_manifest( :paramtype metadata: dict[str, str] :keyword description: A human-readable description of the agent. Default value is None. :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -556,11 +725,9 @@ async def _create_agent_from_manifest( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) if body is _Unset: - if name is _Unset: - raise TypeError("missing required argument: name") if manifest_id is _Unset: raise TypeError("missing required argument: manifest_id") if parameter_values is _Unset: @@ -569,7 +736,6 @@ async def _create_agent_from_manifest( "description": description, "manifest_id": manifest_id, "metadata": metadata, - "name": name, "parameter_values": parameter_values, } body = {k: v for k, v in body.items() if v is not None} @@ -580,7 +746,8 @@ async def _create_agent_from_manifest( else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_agents_create_agent_from_manifest_request( + _request = build_agents_create_version_from_manifest_request( + agent_name=agent_name, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -616,69 +783,23 @@ async def _create_agent_from_manifest( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.AgentVersionDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - async def _update_agent_from_manifest( - self, - agent_name: str, - *, - manifest_id: str, - parameter_values: dict[str, Any], - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - async def _update_agent_from_manifest( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - async def _update_agent_from_manifest( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @distributed_trace_async - async def _update_agent_from_manifest( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - manifest_id: str = _Unset, - parameter_values: dict[str, Any] = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: - """Updates the agent from a manifest by adding a new version if there are any changes to the agent - definition. If no changes, returns the existing agent version. + async def get_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _models.AgentVersionDetails: + """Retrieves a specific version of an agent. - :param agent_name: The name of the agent to update. Required. + :param agent_name: The name of the agent to retrieve. Required. :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword manifest_id: The manifest ID to import the agent version from. Required. - :paramtype manifest_id: str - :keyword parameter_values: The inputs to the manifest that will result in a fully materialized - Agent. Required. - :paramtype parameter_values: dict[str, any] - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :param agent_version: The version of the agent to retrieve. Required. + :type agent_version: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -689,36 +810,15 @@ async def _update_agent_from_manifest( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) - - if body is _Unset: - if manifest_id is _Unset: - raise TypeError("missing required argument: manifest_id") - if parameter_values is _Unset: - raise TypeError("missing required argument: parameter_values") - body = { - "description": description, - "manifest_id": manifest_id, - "metadata": metadata, - "parameter_values": parameter_values, - } - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) - _request = build_agents_update_agent_from_manifest_request( + _request = build_agents_get_version_request( agent_name=agent_name, - content_type=content_type, + agent_version=agent_version, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -751,7 +851,7 @@ async def _update_agent_from_manifest( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.AgentVersionDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -759,13 +859,18 @@ async def _update_agent_from_manifest( return deserialized # type: ignore @distributed_trace_async - async def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: - """Deletes an agent. + async def delete_version( + self, agent_name: str, agent_version: str, **kwargs: Any + ) -> _models.DeleteAgentVersionResponse: + """Deletes a specific version of an agent. :param agent_name: The name of the agent to delete. Required. :type agent_name: str - :return: DeleteAgentResponse. The DeleteAgentResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DeleteAgentResponse + :param agent_version: The version of the agent to delete. Required. + :type agent_version: str + :return: DeleteAgentVersionResponse. The DeleteAgentVersionResponse is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.DeleteAgentVersionResponse :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -779,10 +884,11 @@ async def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentRes _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.DeleteAgentResponse] = kwargs.pop("cls", None) + cls: ClsType[_models.DeleteAgentVersionResponse] = kwargs.pop("cls", None) - _request = build_agents_delete_request( + _request = build_agents_delete_version_request( agent_name=agent_name, + agent_version=agent_version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -816,7 +922,7 @@ async def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentRes if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DeleteAgentResponse, response.json()) + deserialized = _deserialize(_models.DeleteAgentVersionResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -824,20 +930,19 @@ async def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentRes return deserialized # type: ignore @distributed_trace - def list( + def list_versions( self, + agent_name: str, *, - kind: Optional[Union[str, _models.AgentKind]] = None, limit: Optional[int] = None, order: Optional[Union[str, _models.PageOrder]] = None, before: Optional[str] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.AgentDetails"]: - """Returns the list of all agents. + ) -> AsyncItemPaged["_models.AgentVersionDetails"]: + """Returns the list of versions of an agent. - :keyword kind: Filter agents by kind. If not provided, all agents are returned. Known values - are: "prompt", "hosted", and "workflow". Default value is None. - :paramtype kind: str or ~azure.ai.projects.models.AgentKind + :param agent_name: The name of the agent to retrieve versions for. Required. + :type agent_name: str :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. Default value is None. @@ -852,14 +957,14 @@ def list( subsequent call can include before=obj_foo in order to fetch the previous page of the list. Default value is None. :paramtype before: str - :return: An iterator like instance of AgentDetails - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.AgentDetails] + :return: An iterator like instance of AgentVersionDetails + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.AgentVersionDetails] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.AgentDetails]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.AgentVersionDetails]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -871,8 +976,8 @@ def list( def prepare_request(_continuation_token=None): - _request = build_agents_list_request( - kind=kind, + _request = build_agents_list_versions_request( + agent_name=agent_name, limit=limit, order=order, after=_continuation_token, @@ -890,7 +995,7 @@ def prepare_request(_continuation_token=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.AgentDetails], + List[_models.AgentVersionDetails], deserialized.get("data", []), ) if cls: @@ -918,131 +1023,34 @@ async def get_next(_continuation_token=None): return AsyncItemPaged(get_next, extract_data) - @overload - async def create_version( - self, - agent_name: str, - *, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. +class EvaluationRulesOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def create_version( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def create_version( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`evaluation_rules` attribute. + """ - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace_async - async def create_version( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + async def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: + """Get an evaluation rule. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1051,30 +1059,14 @@ async def create_version( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) - - if body is _Unset: - if definition is _Unset: - raise TypeError("missing required argument: definition") - body = {"definition": definition, "description": description, "metadata": metadata} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) - _request = build_agents_create_version_request( - agent_name=agent_name, - foundry_features=_foundry_features, - content_type=content_type, + _request = build_evaluation_rules_get_request( + id=id, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -1098,152 +1090,136 @@ async def create_version( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentVersionDetails, response.json()) + deserialized = _deserialize(_models.EvaluationRule, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - async def create_version_from_manifest( - self, - agent_name: str, - *, - manifest_id: str, - parameter_values: dict[str, Any], - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword manifest_id: The manifest ID to import the agent version from. Required. - :paramtype manifest_id: str - :keyword parameter_values: The inputs to the manifest that will result in a fully materialized - Agent. Required. - :paramtype parameter_values: dict[str, any] - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + @distributed_trace_async + async def delete(self, id: str, **kwargs: Any) -> None: + """Delete an evaluation rule. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :return: None + :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - @overload - async def create_version_from_manifest( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + cls: ClsType[None] = kwargs.pop("cls", None) - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON + _request = build_evaluation_rules_delete_request( + id=id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [204]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if cls: + return cls(pipeline_response, None, {}) # type: ignore + + @overload + async def create_or_update( + self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_version_from_manifest( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. + async def create_or_update( + self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] + @overload + async def create_or_update( + self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create_version_from_manifest( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - manifest_id: str = _Unset, - parameter_values: dict[str, Any] = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword manifest_id: The manifest ID to import the agent version from. Required. - :paramtype manifest_id: str - :keyword parameter_values: The inputs to the manifest that will result in a fully materialized - Agent. Required. - :paramtype parameter_values: dict[str, any] - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + async def create_or_update( + self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Is one of the following types: + EvaluationRule, JSON, IO[bytes] Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1256,29 +1232,18 @@ async def create_version_from_manifest( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) - if body is _Unset: - if manifest_id is _Unset: - raise TypeError("missing required argument: manifest_id") - if parameter_values is _Unset: - raise TypeError("missing required argument: parameter_values") - body = { - "description": description, - "manifest_id": manifest_id, - "metadata": metadata, - "parameter_values": parameter_values, - } - body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(evaluation_rule, (IOBase, bytes)): + _content = evaluation_rule else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(evaluation_rule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_agents_create_version_from_manifest_request( - agent_name=agent_name, + _request = build_evaluation_rules_create_or_update_request( + id=id, + foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -1298,41 +1263,52 @@ async def create_version_from_manifest( response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [200, 201]: if _stream: try: await response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentVersionDetails, response.json()) + deserialized = _deserialize(_models.EvaluationRule, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace_async - async def get_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _models.AgentVersionDetails: - """Retrieves a specific version of an agent. + @distributed_trace + def list( + self, + *, + action_type: Optional[Union[str, _models.EvaluationRuleActionType]] = None, + agent_name: Optional[str] = None, + enabled: Optional[bool] = None, + **kwargs: Any + ) -> AsyncItemPaged["_models.EvaluationRule"]: + """List all evaluation rules. - :param agent_name: The name of the agent to retrieve. Required. - :type agent_name: str - :param agent_version: The version of the agent to retrieve. Required. - :type agent_version: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :keyword action_type: Filter by the type of evaluation rule. Known values are: + "continuousEvaluation" and "humanEvaluationPreview". Default value is None. + :paramtype action_type: str or ~azure.ai.projects.models.EvaluationRuleActionType + :keyword agent_name: Filter by the agent name. Default value is None. + :paramtype agent_name: str + :keyword enabled: Filter by the enabled status. Default value is None. + :paramtype enabled: bool + :return: An iterator like instance of EvaluationRule + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluationRule] :raises ~azure.core.exceptions.HttpResponseError: """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.EvaluationRule]] = kwargs.pop("cls", None) + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1341,67 +1317,99 @@ async def get_version(self, agent_name: str, agent_version: str, **kwargs: Any) } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} + def prepare_request(next_link=None): + if not next_link: - cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) + _request = build_evaluation_rules_list_request( + action_type=action_type, + agent_name=agent_name, + enabled=enabled, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _request = build_agents_get_version_request( - agent_name=agent_name, - agent_version=agent_version, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) + return _request - response = pipeline_response.http_response + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.EvaluationRule], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs ) - raise HttpResponseError(response=response, model=error) + response = pipeline_response.http_response - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.AgentVersionDetails, response.json()) + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return pipeline_response - return deserialized # type: ignore + return AsyncItemPaged(get_next, extract_data) - @distributed_trace_async - async def delete_version( - self, agent_name: str, agent_version: str, **kwargs: Any - ) -> _models.DeleteAgentVersionResponse: - """Deletes a specific version of an agent. - :param agent_name: The name of the agent to delete. Required. - :type agent_name: str - :param agent_version: The version of the agent to delete. Required. - :type agent_version: str - :return: DeleteAgentVersionResponse. The DeleteAgentVersionResponse is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.DeleteAgentVersionResponse +class ConnectionsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`connections` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @distributed_trace_async + async def _get(self, name: str, **kwargs: Any) -> _models.Connection: + """Get a connection by name, without populating connection credentials. + + :param name: The friendly name of the connection, provided by the user. Required. + :type name: str + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -1415,11 +1423,10 @@ async def delete_version( _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.DeleteAgentVersionResponse] = kwargs.pop("cls", None) + cls: ClsType[_models.Connection] = kwargs.pop("cls", None) - _request = build_agents_delete_version_request( - agent_name=agent_name, - agent_version=agent_version, + _request = build_connections_get_request( + name=name, api_version=self._config.api_version, headers=_headers, params=_params, @@ -1444,58 +1451,114 @@ async def delete_version( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) + + response_headers = {} + response_headers["x-ms-client-request-id"] = self._deserialize( + "str", response.headers.get("x-ms-client-request-id") + ) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DeleteAgentVersionResponse, response.json()) + deserialized = _deserialize(_models.Connection, response.json()) if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @distributed_trace_async + async def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: + """Get a connection by name, with its connection credentials. + + :param name: The friendly name of the connection, provided by the user. Required. + :type name: str + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.Connection] = kwargs.pop("cls", None) + + _request = build_connections_get_with_credentials_request( + name=name, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + response_headers = {} + response_headers["x-ms-client-request-id"] = self._deserialize( + "str", response.headers.get("x-ms-client-request-id") + ) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Connection, response.json()) + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore return deserialized # type: ignore @distributed_trace - def list_versions( + def list( self, - agent_name: str, *, - limit: Optional[int] = None, - order: Optional[Union[str, _models.PageOrder]] = None, - before: Optional[str] = None, + connection_type: Optional[Union[str, _models.ConnectionType]] = None, + default_connection: Optional[bool] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.AgentVersionDetails"]: - """Returns the list of versions of an agent. + ) -> AsyncItemPaged["_models.Connection"]: + """List all connections in the project, without populating connection credentials. - :param agent_name: The name of the agent to retrieve versions for. Required. - :type agent_name: str - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the - default is 20. Default value is None. - :paramtype limit: int - :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for - ascending order and``desc`` - for descending order. Known values are: "asc" and "desc". Default value is None. - :paramtype order: str or ~azure.ai.projects.models.PageOrder - :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your - place in the list. - For instance, if you make a list request and receive 100 objects, ending with obj_foo, your - subsequent call can include before=obj_foo in order to fetch the previous page of the list. - Default value is None. - :paramtype before: str - :return: An iterator like instance of AgentVersionDetails - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.AgentVersionDetails] + :keyword connection_type: List connections of this specific type. Known values are: + "AzureOpenAI", "AzureBlob", "AzureStorageAccount", "CognitiveSearch", "CosmosDB", "ApiKey", + "AppConfig", "AppInsights", "CustomKeys", and "RemoteTool_Preview". Default value is None. + :paramtype connection_type: str or ~azure.ai.projects.models.ConnectionType + :keyword default_connection: List connections that are default connections. Default value is + None. + :paramtype default_connection: bool + :return: An iterator like instance of Connection + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Connection] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.AgentVersionDetails]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Connection]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -1505,36 +1568,57 @@ def list_versions( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(_continuation_token=None): + def prepare_request(next_link=None): + if not next_link: + + _request = build_connections_list_request( + connection_type=connection_type, + default_connection=default_connection, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _request = build_agents_list_versions_request( - agent_name=agent_name, - limit=limit, - order=order, - after=_continuation_token, - before=before, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) return _request async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.AgentVersionDetails], - deserialized.get("data", []), + List[_models.Connection], + deserialized.get("value", []), ) if cls: list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("last_id") or None, AsyncList(list_of_elem) + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) - async def get_next(_continuation_token=None): - _request = prepare_request(_continuation_token) + async def get_next(next_link=None): + _request = prepare_request(next_link) _stream = False pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access @@ -1544,25 +1628,21 @@ async def get_next(_continuation_token=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) return pipeline_response return AsyncItemPaged(get_next, extract_data) -class EvaluationRulesOperations: +class DatasetsOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`evaluation_rules` attribute. + :attr:`datasets` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -1572,16 +1652,21 @@ def __init__(self, *args, **kwargs) -> None: self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - @distributed_trace_async - async def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: - """Get an evaluation rule. + @distributed_trace + def list_versions(self, name: str, **kwargs: Any) -> AsyncItemPaged["_models.DatasetVersion"]: + """List all versions of the given DatasetVersion. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :param name: The name of the resource. Required. + :type name: str + :return: An iterator like instance of DatasetVersion + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.DatasetVersion] :raises ~azure.core.exceptions.HttpResponseError: """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1590,13 +1675,185 @@ async def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) + def prepare_request(next_link=None): + if not next_link: - _request = build_evaluation_rules_get_request( - id=id, + _request = build_datasets_list_versions_request( + name=name, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.DatasetVersion], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + + @distributed_trace + def list(self, **kwargs: Any) -> AsyncItemPaged["_models.DatasetVersion"]: + """List the latest version of each DatasetVersion. + + :return: An iterator like instance of DatasetVersion + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.DatasetVersion] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_datasets_list_request( + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.DatasetVersion], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + + @distributed_trace_async + async def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: + """Get the specific version of the DatasetVersion. The service returns 404 Not Found error if the + DatasetVersion does not exist. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to retrieve. Required. + :type version: str + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) + + _request = build_datasets_get_request( + name=name, + version=version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -1626,7 +1883,7 @@ async def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationRule, response.json()) + deserialized = _deserialize(_models.DatasetVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -1634,11 +1891,14 @@ async def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: return deserialized # type: ignore @distributed_trace_async - async def delete(self, id: str, **kwargs: Any) -> None: - """Delete an evaluation rule. + async def delete(self, name: str, version: str, **kwargs: Any) -> None: + """Delete the specific version of the DatasetVersion. The service returns 204 No Content if the + DatasetVersion was deleted successfully or if the DatasetVersion does not exist. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the DatasetVersion to delete. Required. + :type version: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: @@ -1656,8 +1916,9 @@ async def delete(self, id: str, **kwargs: Any) -> None: cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_evaluation_rules_delete_request( - id=id, + _request = build_datasets_delete_request( + name=name, + version=version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -1683,74 +1944,99 @@ async def delete(self, id: str, **kwargs: Any) -> None: @overload async def create_or_update( - self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, + name: str, + version: str, + dataset_version: _models.DatasetVersion, + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Required. + :type dataset_version: ~azure.ai.projects.models.DatasetVersion :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload async def create_or_update( - self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, + name: str, + version: str, + dataset_version: JSON, + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: JSON + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Required. + :type dataset_version: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload async def create_or_update( - self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, + name: str, + version: str, + dataset_version: IO[bytes], + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: IO[bytes] + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Required. + :type dataset_version: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async async def create_or_update( - self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, name: str, version: str, dataset_version: Union[_models.DatasetVersion, JSON, IO[bytes]], **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Is one of the following types: - EvaluationRule, JSON, IO[bytes] Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Is one of the following types: + DatasetVersion, JSON, IO[bytes] Required. + :type dataset_version: ~azure.ai.projects.models.DatasetVersion or JSON or IO[bytes] + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1763,18 +2049,18 @@ async def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) + cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) - content_type = content_type or "application/json" + content_type = content_type or "application/merge-patch+json" _content = None - if isinstance(evaluation_rule, (IOBase, bytes)): - _content = evaluation_rule + if isinstance(dataset_version, (IOBase, bytes)): + _content = dataset_version else: - _content = json.dumps(evaluation_rule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(dataset_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_evaluation_rules_create_or_update_request( - id=id, - foundry_features=_foundry_features, + _request = build_datasets_create_or_update_request( + name=name, + version=version, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -1806,40 +2092,113 @@ async def create_or_update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationRule, response.json()) + deserialized = _deserialize(_models.DatasetVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace - def list( + @overload + async def pending_upload( self, + name: str, + version: str, + pending_upload_request: _models.PendingUploadRequest, *, - action_type: Optional[Union[str, _models.EvaluationRuleActionType]] = None, - agent_name: Optional[str] = None, - enabled: Optional[bool] = None, + content_type: str = "application/json", **kwargs: Any - ) -> AsyncItemPaged["_models.EvaluationRule"]: - """List all evaluation rules. + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. - :keyword action_type: Filter by the type of evaluation rule. Known values are: - "continuousEvaluation" and "humanEvaluationPreview". Default value is None. - :paramtype action_type: str or ~azure.ai.projects.models.EvaluationRuleActionType - :keyword agent_name: Filter by the agent name. Default value is None. - :paramtype agent_name: str - :keyword enabled: Filter by the enabled status. Default value is None. - :paramtype enabled: bool - :return: An iterator like instance of EvaluationRule - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluationRule] + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.EvaluationRule]] = kwargs.pop("cls", None) + @overload + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Is one of the following + types: PendingUploadRequest, JSON, IO[bytes] Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or + IO[bytes] + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1848,99 +2207,70 @@ def list( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(next_link=None): - if not next_link: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - _request = build_evaluation_rules_list_request( - action_type=action_type, - agent_name=agent_name, - enabled=enabled, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type = content_type or "application/json" + _content = None + if isinstance(pending_upload_request, (IOBase, bytes)): + _content = pending_upload_request + else: + _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - return _request + _request = build_datasets_pending_upload_request( + name=name, + version=version, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - async def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.EvaluationRule], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) - async def get_next(next_link=None): - _request = prepare_request(next_link) - - _stream = False - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - return pipeline_response - - return AsyncItemPaged(get_next, extract_data) + response = pipeline_response.http_response + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) -class ConnectionsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.PendingUploadResponse, response.json()) - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`connections` attribute. - """ + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + return deserialized # type: ignore @distributed_trace_async - async def _get(self, name: str, **kwargs: Any) -> _models.Connection: - """Get a connection by name, without populating connection credentials. + async def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with a Dataset version. - :param name: The friendly name of the connection, provided by the user. Required. + :param name: The name of the resource. Required. :type name: str - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -1954,10 +2284,11 @@ async def _get(self, name: str, **kwargs: Any) -> _models.Connection: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Connection] = kwargs.pop("cls", None) + cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) - _request = build_connections_get_request( + _request = build_datasets_get_credentials_request( name=name, + version=version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -1984,29 +2315,42 @@ async def _get(self, name: str, **kwargs: Any) -> _models.Connection: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) - response_headers = {} - response_headers["x-ms-client-request-id"] = self._deserialize( - "str", response.headers.get("x-ms-client-request-id") - ) - if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Connection, response.json()) + deserialized = _deserialize(_models.DatasetCredential, response.json()) if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + +class DeploymentsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`deployments` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @distributed_trace_async - async def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: - """Get a connection by name, with its connection credentials. + async def get(self, name: str, **kwargs: Any) -> _models.Deployment: + """Get a deployed model. - :param name: The friendly name of the connection, provided by the user. Required. + :param name: Name of the deployment. Required. :type name: str - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection + :return: Deployment. The Deployment is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Deployment :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2020,9 +2364,9 @@ async def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Conne _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Connection] = kwargs.pop("cls", None) + cls: ClsType[_models.Deployment] = kwargs.pop("cls", None) - _request = build_connections_get_with_credentials_request( + _request = build_deployments_get_request( name=name, api_version=self._config.api_version, headers=_headers, @@ -2058,7 +2402,7 @@ async def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Conne if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Connection, response.json()) + deserialized = _deserialize(_models.Deployment, response.json()) if cls: return cls(pipeline_response, deserialized, response_headers) # type: ignore @@ -2069,27 +2413,29 @@ async def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Conne def list( self, *, - connection_type: Optional[Union[str, _models.ConnectionType]] = None, - default_connection: Optional[bool] = None, + model_publisher: Optional[str] = None, + model_name: Optional[str] = None, + deployment_type: Optional[Union[str, _models.DeploymentType]] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.Connection"]: - """List all connections in the project, without populating connection credentials. + ) -> AsyncItemPaged["_models.Deployment"]: + """List all deployed models in the project. - :keyword connection_type: List connections of this specific type. Known values are: - "AzureOpenAI", "AzureBlob", "AzureStorageAccount", "CognitiveSearch", "CosmosDB", "ApiKey", - "AppConfig", "AppInsights", "CustomKeys", and "RemoteTool_Preview". Default value is None. - :paramtype connection_type: str or ~azure.ai.projects.models.ConnectionType - :keyword default_connection: List connections that are default connections. Default value is - None. - :paramtype default_connection: bool - :return: An iterator like instance of Connection - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Connection] + :keyword model_publisher: Model publisher to filter models by. Default value is None. + :paramtype model_publisher: str + :keyword model_name: Model name (the publisher specific name) to filter models by. Default + value is None. + :paramtype model_name: str + :keyword deployment_type: Type of deployment to filter list by. "ModelDeployment" Default value + is None. + :paramtype deployment_type: str or ~azure.ai.projects.models.DeploymentType + :return: An iterator like instance of Deployment + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Deployment] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Connection]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Deployment]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -2102,9 +2448,10 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_connections_list_request( - connection_type=connection_type, - default_connection=default_connection, + _request = build_deployments_list_request( + model_publisher=model_publisher, + model_name=model_name, + deployment_type=deployment_type, api_version=self._config.api_version, headers=_headers, params=_params, @@ -2141,7 +2488,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Connection], + List[_models.Deployment], deserialized.get("value", []), ) if cls: @@ -2166,14 +2513,14 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) -class DatasetsOperations: +class IndexesOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`datasets` attribute. + :attr:`indexes` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -2184,19 +2531,19 @@ def __init__(self, *args, **kwargs) -> None: self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def list_versions(self, name: str, **kwargs: Any) -> AsyncItemPaged["_models.DatasetVersion"]: - """List all versions of the given DatasetVersion. + def list_versions(self, name: str, **kwargs: Any) -> AsyncItemPaged["_models.Index"]: + """List all versions of the given Index. :param name: The name of the resource. Required. :type name: str - :return: An iterator like instance of DatasetVersion - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.DatasetVersion] + :return: An iterator like instance of Index + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Index] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -2209,7 +2556,7 @@ def list_versions(self, name: str, **kwargs: Any) -> AsyncItemPaged["_models.Dat def prepare_request(next_link=None): if not next_link: - _request = build_datasets_list_versions_request( + _request = build_indexes_list_versions_request( name=name, api_version=self._config.api_version, headers=_headers, @@ -2247,7 +2594,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.DatasetVersion], + List[_models.Index], deserialized.get("value", []), ) if cls: @@ -2272,17 +2619,17 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) @distributed_trace - def list(self, **kwargs: Any) -> AsyncItemPaged["_models.DatasetVersion"]: - """List the latest version of each DatasetVersion. + def list(self, **kwargs: Any) -> AsyncItemPaged["_models.Index"]: + """List the latest version of each Index. - :return: An iterator like instance of DatasetVersion - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.DatasetVersion] + :return: An iterator like instance of Index + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Index] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -2295,7 +2642,7 @@ def list(self, **kwargs: Any) -> AsyncItemPaged["_models.DatasetVersion"]: def prepare_request(next_link=None): if not next_link: - _request = build_datasets_list_request( + _request = build_indexes_list_request( api_version=self._config.api_version, headers=_headers, params=_params, @@ -2332,7 +2679,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.DatasetVersion], + List[_models.Index], deserialized.get("value", []), ) if cls: @@ -2357,16 +2704,16 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) @distributed_trace_async - async def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: - """Get the specific version of the DatasetVersion. The service returns 404 Not Found error if the - DatasetVersion does not exist. + async def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: + """Get the specific version of the Index. The service returns 404 Not Found error if the Index + does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to retrieve. Required. + :param version: The specific version id of the Index to retrieve. Required. :type version: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2380,9 +2727,9 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVe _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) + cls: ClsType[_models.Index] = kwargs.pop("cls", None) - _request = build_datasets_get_request( + _request = build_indexes_get_request( name=name, version=version, api_version=self._config.api_version, @@ -2414,7 +2761,7 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVe if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DatasetVersion, response.json()) + deserialized = _deserialize(_models.Index, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -2423,12 +2770,12 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVe @distributed_trace_async async def delete(self, name: str, version: str, **kwargs: Any) -> None: - """Delete the specific version of the DatasetVersion. The service returns 204 No Content if the - DatasetVersion was deleted successfully or if the DatasetVersion does not exist. + """Delete the specific version of the Index. The service returns 204 No Content if the Index was + deleted successfully or if the Index does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The version of the DatasetVersion to delete. Required. + :param version: The version of the Index to delete. Required. :type version: str :return: None :rtype: None @@ -2447,7 +2794,7 @@ async def delete(self, name: str, version: str, **kwargs: Any) -> None: cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_datasets_delete_request( + _request = build_indexes_delete_request( name=name, version=version, api_version=self._config.api_version, @@ -2478,50 +2825,44 @@ async def create_or_update( self, name: str, version: str, - dataset_version: _models.DatasetVersion, + index: _models.Index, *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Required. - :type dataset_version: ~azure.ai.projects.models.DatasetVersion + :param index: The Index to create or update. Required. + :type index: ~azure.ai.projects.models.Index :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ @overload async def create_or_update( - self, - name: str, - version: str, - dataset_version: JSON, - *, - content_type: str = "application/merge-patch+json", - **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + self, name: str, version: str, index: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Required. - :type dataset_version: JSON + :param index: The Index to create or update. Required. + :type index: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ @@ -2530,42 +2871,42 @@ async def create_or_update( self, name: str, version: str, - dataset_version: IO[bytes], + index: IO[bytes], *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Required. - :type dataset_version: IO[bytes] + :param index: The Index to create or update. Required. + :type index: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async async def create_or_update( - self, name: str, version: str, dataset_version: Union[_models.DatasetVersion, JSON, IO[bytes]], **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + self, name: str, version: str, index: Union[_models.Index, JSON, IO[bytes]], **kwargs: Any + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Is one of the following types: - DatasetVersion, JSON, IO[bytes] Required. - :type dataset_version: ~azure.ai.projects.models.DatasetVersion or JSON or IO[bytes] - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :param index: The Index to create or update. Is one of the following types: Index, JSON, + IO[bytes] Required. + :type index: ~azure.ai.projects.models.Index or JSON or IO[bytes] + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2580,16 +2921,16 @@ async def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) + cls: ClsType[_models.Index] = kwargs.pop("cls", None) content_type = content_type or "application/merge-patch+json" _content = None - if isinstance(dataset_version, (IOBase, bytes)): - _content = dataset_version + if isinstance(index, (IOBase, bytes)): + _content = index else: - _content = json.dumps(dataset_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(index, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_datasets_create_or_update_request( + _request = build_indexes_create_or_update_request( name=name, version=version, content_type=content_type, @@ -2623,113 +2964,44 @@ async def create_or_update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DatasetVersion, response.json()) + deserialized = _deserialize(_models.Index, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - async def pending_upload( - self, - name: str, - version: str, - pending_upload_request: _models.PendingUploadRequest, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def pending_upload( - self, - name: str, - version: str, - pending_upload_request: JSON, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ +class BetaEvaluationTaxonomiesOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - @overload - async def pending_upload( - self, - name: str, - version: str, - pending_upload_request: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`evaluation_taxonomies` attribute. + """ - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace_async - async def pending_upload( - self, - name: str, - version: str, - pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. + async def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: + """Get an evaluation run by name. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Is one of the following - types: PendingUploadRequest, JSON, IO[bytes] Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or - IO[bytes] - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -2738,25 +3010,15 @@ async def pending_upload( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) - - content_type = content_type or "application/json" - _content = None - if isinstance(pending_upload_request, (IOBase, bytes)): - _content = pending_upload_request - else: - _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) - _request = build_datasets_pending_upload_request( + _request = build_beta_evaluation_taxonomies_get_request( name=name, - version=version, - content_type=content_type, + foundry_features=_foundry_features, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -2785,25 +3047,35 @@ async def pending_upload( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.PendingUploadResponse, response.json()) + deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace_async - async def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with a Dataset version. + @distributed_trace + def list( + self, *, input_name: Optional[str] = None, input_type: Optional[str] = None, **kwargs: Any + ) -> AsyncItemPaged["_models.EvaluationTaxonomy"]: + """List evaluation taxonomies. - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential + :keyword input_name: Filter by the evaluation input name. Default value is None. + :paramtype input_name: str + :keyword input_type: Filter by taxonomy input type. Default value is None. + :paramtype input_type: str + :return: An iterator like instance of EvaluationTaxonomy + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluationTaxonomy] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.EvaluationTaxonomy]] = kwargs.pop("cls", None) + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -2812,78 +3084,200 @@ async def get_credentials(self, name: str, version: str, **kwargs: Any) -> _mode } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} + def prepare_request(next_link=None): + if not next_link: - cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) + _request = build_beta_evaluation_taxonomies_list_request( + foundry_features=_foundry_features, + input_name=input_name, + input_type=input_type, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _request = build_datasets_get_credentials_request( - name=name, - version=version, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) + return _request - response = pipeline_response.http_response + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.EvaluationTaxonomy], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, AsyncList(list_of_elem) - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass + async def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) + + @distributed_trace_async + async def delete(self, name: str, **kwargs: Any) -> None: + """Delete an evaluation taxonomy by name. + + :param name: The name of the resource. Required. + :type name: str + :return: None + :rtype: None + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[None] = kwargs.pop("cls", None) + + _request = build_beta_evaluation_taxonomies_delete_request( + name=name, + foundry_features=_foundry_features, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.DatasetCredential, response.json()) - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return cls(pipeline_response, None, {}) # type: ignore - return deserialized # type: ignore + @overload + async def create( + self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ -class DeploymentsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + @overload + async def create( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`deployments` attribute. - """ + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @overload + async def create( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. + + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace_async - async def get(self, name: str, **kwargs: Any) -> _models.Deployment: - """Get a deployed model. + async def create( + self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. - :param name: Name of the deployment. Required. + :param name: The name of the evaluation taxonomy. Required. :type name: str - :return: Deployment. The Deployment is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Deployment + :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, + IO[bytes] Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -2892,14 +3286,25 @@ async def get(self, name: str, **kwargs: Any) -> _models.Deployment: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Deployment] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) - _request = build_deployments_get_request( + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluation_taxonomies_create_request( name=name, + foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -2916,7 +3321,7 @@ async def get(self, name: str, **kwargs: Any) -> _models.Deployment: response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [200, 201]: if _stream: try: await response.read() # Load the body in memory and close the socket @@ -2925,133 +3330,159 @@ async def get(self, name: str, **kwargs: Any) -> _models.Deployment: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) - response_headers = {} - response_headers["x-ms-client-request-id"] = self._deserialize( - "str", response.headers.get("x-ms-client-request-id") - ) - if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Deployment, response.json()) + deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace - def list( - self, - *, - model_publisher: Optional[str] = None, - model_name: Optional[str] = None, - deployment_type: Optional[Union[str, _models.DeploymentType]] = None, - **kwargs: Any - ) -> AsyncItemPaged["_models.Deployment"]: - """List all deployed models in the project. + @overload + async def update( + self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - :keyword model_publisher: Model publisher to filter models by. Default value is None. - :paramtype model_publisher: str - :keyword model_name: Model name (the publisher specific name) to filter models by. Default - value is None. - :paramtype model_name: str - :keyword deployment_type: Type of deployment to filter list by. "ModelDeployment" Default value - is None. - :paramtype deployment_type: str or ~azure.ai.projects.models.DeploymentType - :return: An iterator like instance of Deployment - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Deployment] + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Deployment]] = kwargs.pop("cls", None) + @overload + async def update( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ - def prepare_request(next_link=None): - if not next_link: + @overload + async def update( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - _request = build_deployments_list_request( - model_publisher=model_publisher, - model_name=model_name, - deployment_type=deployment_type, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + @distributed_trace_async + async def update( + self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - return _request + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, + IO[bytes] Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - async def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.Deployment], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - async def get_next(next_link=None): - _request = prepare_request(next_link) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) - _stream = False - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + _request = build_beta_evaluation_taxonomies_update_request( + name=name, + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - return pipeline_response + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) - return AsyncItemPaged(get_next, extract_data) + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) -class IndexesOperations: + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + +class BetaEvaluatorsOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`indexes` attribute. + :attr:`evaluators` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -3062,19 +3493,36 @@ def __init__(self, *args, **kwargs) -> None: self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def list_versions(self, name: str, **kwargs: Any) -> AsyncItemPaged["_models.Index"]: - """List all versions of the given Index. + def list_versions( + self, + name: str, + *, + type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, + limit: Optional[int] = None, + **kwargs: Any + ) -> AsyncItemPaged["_models.EvaluatorVersion"]: + """List all versions of the given evaluator. :param name: The name of the resource. Required. :type name: str - :return: An iterator like instance of Index - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Index] + :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one + of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default + value is None. + :paramtype type: str or str or str or str + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. Default value is None. + :paramtype limit: int + :return: An iterator like instance of EvaluatorVersion + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -3087,8 +3535,11 @@ def list_versions(self, name: str, **kwargs: Any) -> AsyncItemPaged["_models.Ind def prepare_request(next_link=None): if not next_link: - _request = build_indexes_list_versions_request( + _request = build_beta_evaluators_list_versions_request( name=name, + foundry_features=_foundry_features, + type=type, + limit=limit, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3111,7 +3562,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3125,7 +3579,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Index], + List[_models.EvaluatorVersion], deserialized.get("value", []), ) if cls: @@ -3150,17 +3604,33 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) @distributed_trace - def list(self, **kwargs: Any) -> AsyncItemPaged["_models.Index"]: - """List the latest version of each Index. + def list( + self, + *, + type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, + limit: Optional[int] = None, + **kwargs: Any + ) -> AsyncItemPaged["_models.EvaluatorVersion"]: + """List the latest version of each evaluator. - :return: An iterator like instance of Index - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Index] + :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one + of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default + value is None. + :paramtype type: str or str or str or str + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. Default value is None. + :paramtype limit: int + :return: An iterator like instance of EvaluatorVersion + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -3173,7 +3643,10 @@ def list(self, **kwargs: Any) -> AsyncItemPaged["_models.Index"]: def prepare_request(next_link=None): if not next_link: - _request = build_indexes_list_request( + _request = build_beta_evaluators_list_request( + foundry_features=_foundry_features, + type=type, + limit=limit, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3196,7 +3669,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3210,7 +3686,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Index], + List[_models.EvaluatorVersion], deserialized.get("value", []), ) if cls: @@ -3235,18 +3711,21 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) @distributed_trace_async - async def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: - """Get the specific version of the Index. The service returns 404 Not Found error if the Index - does not exist. + async def get_version(self, name: str, version: str, **kwargs: Any) -> _models.EvaluatorVersion: + """Get the specific version of the EvaluatorVersion. The service returns 404 Not Found error if + the EvaluatorVersion does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to retrieve. Required. + :param version: The specific version id of the EvaluatorVersion to retrieve. Required. :type version: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3258,11 +3737,12 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Index] = kwargs.pop("cls", None) + cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - _request = build_indexes_get_request( + _request = build_beta_evaluators_get_version_request( name=name, version=version, + foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3292,7 +3772,7 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Index, response.json()) + deserialized = _deserialize(_models.EvaluatorVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -3300,18 +3780,21 @@ async def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: return deserialized # type: ignore @distributed_trace_async - async def delete(self, name: str, version: str, **kwargs: Any) -> None: - """Delete the specific version of the Index. The service returns 204 No Content if the Index was - deleted successfully or if the Index does not exist. + async def delete_version(self, name: str, version: str, **kwargs: Any) -> None: + """Delete the specific version of the EvaluatorVersion. The service returns 204 No Content if the + EvaluatorVersion was deleted successfully or if the EvaluatorVersion does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The version of the Index to delete. Required. + :param version: The version of the EvaluatorVersion to delete. Required. :type version: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3325,9 +3808,10 @@ async def delete(self, name: str, version: str, **kwargs: Any) -> None: cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_indexes_delete_request( + _request = build_beta_evaluators_delete_version_request( name=name, version=version, + foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3352,94 +3836,82 @@ async def delete(self, name: str, version: str, **kwargs: Any) -> None: return cls(pipeline_response, None, {}) # type: ignore @overload - async def create_or_update( + async def create_version( self, name: str, - version: str, - index: _models.Index, + evaluator_version: _models.EvaluatorVersion, *, - content_type: str = "application/merge-patch+json", + content_type: str = "application/json", **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Required. - :type index: ~azure.ai.projects.models.Index + :param evaluator_version: Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/merge-patch+json". + Default value is "application/json". :paramtype content_type: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_or_update( - self, name: str, version: str, index: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + async def create_version( + self, name: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Required. - :type index: JSON + :param evaluator_version: Required. + :type evaluator_version: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/merge-patch+json". + Default value is "application/json". :paramtype content_type: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_or_update( - self, - name: str, - version: str, - index: IO[bytes], - *, - content_type: str = "application/merge-patch+json", - **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + async def create_version( + self, name: str, evaluator_version: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Required. - :type index: IO[bytes] + :param evaluator_version: Required. + :type evaluator_version: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/merge-patch+json". + Default value is "application/json". :paramtype content_type: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create_or_update( - self, name: str, version: str, index: Union[_models.Index, JSON, IO[bytes]], **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + async def create_version( + self, name: str, evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Is one of the following types: Index, JSON, - IO[bytes] Required. - :type index: ~azure.ai.projects.models.Index or JSON or IO[bytes] - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :param evaluator_version: Is one of the following types: EvaluatorVersion, JSON, IO[bytes] + Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3452,18 +3924,18 @@ async def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.Index] = kwargs.pop("cls", None) + cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - content_type = content_type or "application/merge-patch+json" + content_type = content_type or "application/json" _content = None - if isinstance(index, (IOBase, bytes)): - _content = index + if isinstance(evaluator_version, (IOBase, bytes)): + _content = evaluator_version else: - _content = json.dumps(index, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_indexes_create_or_update_request( + _request = build_beta_evaluators_create_version_request( name=name, - version=version, + foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3483,7 +3955,7 @@ async def create_or_update( response = pipeline_response.http_response - if response.status_code not in [200, 201]: + if response.status_code not in [201]: if _stream: try: await response.read() # Load the body in memory and close the socket @@ -3495,39 +3967,104 @@ async def create_or_update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Index, response.json()) + deserialized = _deserialize(_models.EvaluatorVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + @overload + async def update_version( + self, + name: str, + version: str, + evaluator_version: _models.EvaluatorVersion, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. -class BetaEvaluationTaxonomiesOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`evaluation_taxonomies` attribute. - """ + @overload + async def update_version( + self, name: str, version: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Required. + :type evaluator_version: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def update_version( + self, + name: str, + version: str, + evaluator_version: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. + + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Required. + :type evaluator_version: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace_async - async def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: - """Get an evaluation run by name. + async def update_version( + self, + name: str, + version: str, + evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. :param name: The name of the resource. Required. :type name: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Is one of the following types: EvaluatorVersion, + JSON, IO[bytes] Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( @@ -3541,15 +4078,26 @@ async def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - _request = build_beta_evaluation_taxonomies_get_request( + content_type = content_type or "application/json" + _content = None + if isinstance(evaluator_version, (IOBase, bytes)): + _content = evaluator_version + else: + _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluators_update_version_request( name=name, + version=version, foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -3578,232 +4126,111 @@ async def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) + deserialized = _deserialize(_models.EvaluatorVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace - def list( - self, *, input_name: Optional[str] = None, input_type: Optional[str] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.EvaluationTaxonomy"]: - """List evaluation taxonomies. - - :keyword input_name: Filter by the evaluation input name. Default value is None. - :paramtype input_name: str - :keyword input_type: Filter by taxonomy input type. Default value is None. - :paramtype input_type: str - :return: An iterator like instance of EvaluationTaxonomy - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluationTaxonomy] - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[List[_models.EvaluationTaxonomy]] = kwargs.pop("cls", None) - - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - def prepare_request(next_link=None): - if not next_link: - - _request = build_beta_evaluation_taxonomies_list_request( - foundry_features=_foundry_features, - input_name=input_name, - input_type=input_type, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - return _request - - async def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.EvaluationTaxonomy], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, AsyncList(list_of_elem) - - async def get_next(next_link=None): - _request = prepare_request(next_link) - - _stream = False - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - return pipeline_response - - return AsyncItemPaged(get_next, extract_data) - - @distributed_trace_async - async def delete(self, name: str, **kwargs: Any) -> None: - """Delete an evaluation taxonomy by name. + @overload + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: _models.PendingUploadRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. :param name: The name of the resource. Required. :type name: str - :return: None - :rtype: None - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[None] = kwargs.pop("cls", None) - - _request = build_beta_evaluation_taxonomies_delete_request( - name=name, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _stream = False - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [204]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - if cls: - return cls(pipeline_response, None, {}) # type: ignore - - @overload - async def create( - self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. - - :param name: The name of the evaluation taxonomy. Required. - :type name: str - :param body: The evaluation taxonomy. Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: JSON + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: IO[bytes] + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create( - self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. + async def pending_upload( + self, + name: str, + version: str, + pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, - IO[bytes] Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Is one of the following + types: PendingUploadRequest, JSON, IO[bytes] Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or + IO[bytes] + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( @@ -3821,17 +4248,18 @@ async def create( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) + cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(pending_upload_request, (IOBase, bytes)): + _content = pending_upload_request else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluation_taxonomies_create_request( + _request = build_beta_evaluators_pending_upload_request( name=name, + version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -3852,7 +4280,7 @@ async def create( response = pipeline_response.http_response - if response.status_code not in [200, 201]: + if response.status_code not in [200]: if _stream: try: await response.read() # Load the body in memory and close the socket @@ -3864,7 +4292,7 @@ async def create( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) + deserialized = _deserialize(_models.PendingUploadResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -3872,72 +4300,103 @@ async def create( return deserialized # type: ignore @overload - async def update( - self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + async def get_credentials( + self, + name: str, + version: str, + credential_request: _models.EvaluatorCredentialRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def update( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + async def get_credentials( + self, + name: str, + version: str, + credential_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: JSON + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def update( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + async def get_credentials( + self, + name: str, + version: str, + credential_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: IO[bytes] + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def update( - self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + async def get_credentials( + self, + name: str, + version: str, + credential_request: Union[_models.EvaluatorCredentialRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, - IO[bytes] Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Is one of the following types: + EvaluatorCredentialRequest, JSON, IO[bytes] Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest or JSON or + IO[bytes] + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( @@ -3955,17 +4414,18 @@ async def update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) + cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(credential_request, (IOBase, bytes)): + _content = credential_request else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(credential_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluation_taxonomies_update_request( + _request = build_beta_evaluators_get_credentials_request( name=name, + version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -3998,7 +4458,7 @@ async def update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) + deserialized = _deserialize(_models.DatasetCredential, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -4006,14 +4466,14 @@ async def update( return deserialized # type: ignore -class BetaEvaluatorsOperations: +class BetaInsightsOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`evaluators` attribute. + :attr:`insights` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -4023,38 +4483,71 @@ def __init__(self, *args, **kwargs) -> None: self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - @distributed_trace - def list_versions( - self, - name: str, - *, - type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, - limit: Optional[int] = None, - **kwargs: Any - ) -> AsyncItemPaged["_models.EvaluatorVersion"]: - """List all versions of the given evaluator. + @overload + async def generate( + self, insight: _models.Insight, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Insight: + """Generate Insights. - :param name: The name of the resource. Required. - :type name: str - :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one - of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default - value is None. - :paramtype type: str or str or str or str - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the default is 20. Default value is None. - :paramtype limit: int - :return: An iterator like instance of EvaluatorVersion - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluatorVersion] + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Required. + :type insight: ~azure.ai.projects.models.Insight + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) + @overload + async def generate( + self, insight: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Insight: + """Generate Insights. + + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Required. + :type insight: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def generate( + self, insight: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Insight: + """Generate Insights. + + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Required. + :type insight: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: Any) -> _models.Insight: + """Generate Insights. + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Is one of the following types: Insight, JSON, IO[bytes] Required. + :type insight: ~azure.ai.projects.models.Insight or JSON or IO[bytes] + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4063,105 +4556,166 @@ def list_versions( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(next_link=None): - if not next_link: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - _request = build_beta_evaluators_list_versions_request( - name=name, - foundry_features=_foundry_features, - type=type, - limit=limit, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.Insight] = kwargs.pop("cls", None) - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type = content_type or "application/json" + _content = None + if isinstance(insight, (IOBase, bytes)): + _content = insight + else: + _content = json.dumps(insight, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - return _request + _request = build_beta_insights_generate_request( + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - async def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.EvaluatorVersion], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) - async def get_next(next_link=None): - _request = prepare_request(next_link) + response = pipeline_response.http_response - _stream = False - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response + if response.status_code not in [201]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Insight, response.json()) - return pipeline_response + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore - return AsyncItemPaged(get_next, extract_data) + return deserialized # type: ignore + + @distributed_trace_async + async def get( + self, insight_id: str, *, include_coordinates: Optional[bool] = None, **kwargs: Any + ) -> _models.Insight: + """Get a specific insight by Id. + + :param insight_id: The unique identifier for the insights report. Required. + :type insight_id: str + :keyword include_coordinates: Whether to include coordinates for visualization in the response. + Defaults to false. Default value is None. + :paramtype include_coordinates: bool + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.Insight] = kwargs.pop("cls", None) + + _request = build_beta_insights_get_request( + insight_id=insight_id, + foundry_features=_foundry_features, + include_coordinates=include_coordinates, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Insight, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @distributed_trace def list( self, *, - type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, - limit: Optional[int] = None, + type: Optional[Union[str, _models.InsightType]] = None, + eval_id: Optional[str] = None, + run_id: Optional[str] = None, + agent_name: Optional[str] = None, + include_coordinates: Optional[bool] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.EvaluatorVersion"]: - """List the latest version of each evaluator. + ) -> AsyncItemPaged["_models.Insight"]: + """List all insights in reverse chronological order (newest first). - :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one - of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default - value is None. - :paramtype type: str or str or str or str - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the default is 20. Default value is None. - :paramtype limit: int - :return: An iterator like instance of EvaluatorVersion - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluatorVersion] + :keyword type: Filter by the type of analysis. Known values are: "EvaluationRunClusterInsight", + "AgentClusterInsight", and "EvaluationComparison". Default value is None. + :paramtype type: str or ~azure.ai.projects.models.InsightType + :keyword eval_id: Filter by the evaluation ID. Default value is None. + :paramtype eval_id: str + :keyword run_id: Filter by the evaluation run ID. Default value is None. + :paramtype run_id: str + :keyword agent_name: Filter by the agent name. Default value is None. + :paramtype agent_name: str + :keyword include_coordinates: Whether to include coordinates for visualization in the response. + Defaults to false. Default value is None. + :paramtype include_coordinates: bool + :return: An iterator like instance of Insight + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Insight] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Insight]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4174,10 +4728,13 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_beta_evaluators_list_request( + _request = build_beta_insights_list_request( foundry_features=_foundry_features, type=type, - limit=limit, + eval_id=eval_id, + run_id=run_id, + agent_name=agent_name, + include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, params=_params, @@ -4217,7 +4774,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.EvaluatorVersion], + List[_models.Insight], deserialized.get("value", []), ) if cls: @@ -4241,90 +4798,116 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) - @distributed_trace_async - async def get_version(self, name: str, version: str, **kwargs: Any) -> _models.EvaluatorVersion: - """Get the specific version of the EvaluatorVersion. The service returns 404 Not Found error if - the EvaluatorVersion does not exist. - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to retrieve. Required. - :type version: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) +class BetaMemoryStoresOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`memory_stores` attribute. + """ - cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - _request = build_beta_evaluators_get_version_request( - name=name, - version=version, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + @overload + async def create( + self, + *, + name: str, + definition: _models.MemoryStoreDefinition, + content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) + :keyword name: The name of the memory store. Required. + :paramtype name: str + :keyword definition: The memory store definition. Required. + :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ - response = pipeline_response.http_response + @overload + async def create( + self, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.EvaluatorVersion, response.json()) + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + @overload + async def create( + self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - return deserialized # type: ignore + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace_async - async def delete_version(self, name: str, version: str, **kwargs: Any) -> None: - """Delete the specific version of the EvaluatorVersion. The service returns 204 No Content if the - EvaluatorVersion was deleted successfully or if the EvaluatorVersion does not exist. + async def create( + self, + body: Union[JSON, IO[bytes]] = _Unset, + *, + name: str = _Unset, + definition: _models.MemoryStoreDefinition = _Unset, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - :param name: The name of the resource. Required. - :type name: str - :param version: The version of the EvaluatorVersion to delete. Required. - :type version: str - :return: None - :rtype: None + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword name: The name of the memory store. Required. + :paramtype name: str + :keyword definition: The memory store definition. Required. + :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4334,16 +4917,31 @@ async def delete_version(self, name: str, version: str, **kwargs: Any) -> None: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[None] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - _request = build_beta_evaluators_delete_version_request( - name=name, - version=version, + if body is _Unset: + if name is _Unset: + raise TypeError("missing required argument: name") + if definition is _Unset: + raise TypeError("missing required argument: definition") + body = {"definition": definition, "description": description, "metadata": metadata, "name": name} + body = {k: v for k, v in body.items() if v is not None} + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_memory_stores_create_request( foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -4352,96 +4950,127 @@ async def delete_version(self, name: str, version: str, **kwargs: Any) -> None: } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [204]: + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @overload - async def create_version( + async def update( self, name: str, - evaluator_version: _models.EvaluatorVersion, *, content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_version( - self, name: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + async def update( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Required. - :type evaluator_version: JSON + :param body: Required. + :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_version( - self, name: str, evaluator_version: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + async def update( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Required. - :type evaluator_version: IO[bytes] + :param body: Required. + :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create_version( - self, name: str, evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + async def update( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Is one of the following types: EvaluatorVersion, JSON, IO[bytes] - Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4455,16 +5084,19 @@ async def create_version( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) + cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) + if body is _Unset: + body = {"description": description, "metadata": metadata} + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(evaluator_version, (IOBase, bytes)): - _content = evaluator_version + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluators_create_version_request( + _request = build_beta_memory_stores_update_request( name=name, foundry_features=_foundry_features, content_type=content_type, @@ -4486,120 +5118,204 @@ async def create_version( response = pipeline_response.http_response - if response.status_code not in [201]: + if response.status_code not in [200]: if _stream: try: await response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluatorVersion, response.json()) + deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - async def update_version( - self, - name: str, - version: str, - evaluator_version: _models.EvaluatorVersion, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + @distributed_trace_async + async def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: + """Retrieve a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to retrieve. Required. :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - @overload - async def update_version( - self, name: str, version: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} - :param name: The name of the resource. Required. - :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Required. - :type evaluator_version: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: - """ + cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - @overload - async def update_version( + _request = build_beta_memory_stores_get_request( + name=name, + foundry_features=_foundry_features, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + def list( self, - name: str, - version: str, - evaluator_version: IO[bytes], *, - content_type: str = "application/json", + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + before: Optional[str] = None, **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + ) -> AsyncItemPaged["_models.MemoryStoreDetails"]: + """List all memory stores. - :param name: The name of the resource. Required. - :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Required. - :type evaluator_version: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the + default is 20. Default value is None. + :paramtype limit: int + :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for + ascending order and``desc`` + for descending order. Known values are: "asc" and "desc". Default value is None. + :paramtype order: str or ~azure.ai.projects.models.PageOrder + :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your + place in the list. + For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page of the list. + Default value is None. + :paramtype before: str + :return: An iterator like instance of MemoryStoreDetails + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.MemoryStoreDetails] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + ) + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.MemoryStoreDetails]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(_continuation_token=None): + + _request = build_beta_memory_stores_list_request( + foundry_features=_foundry_features, + limit=limit, + order=order, + after=_continuation_token, + before=before, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + return _request + + async def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.MemoryStoreDetails], + deserialized.get("data", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("last_id") or None, AsyncList(list_of_elem) + + async def get_next(_continuation_token=None): + _request = prepare_request(_continuation_token) + + _stream = False + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + return pipeline_response + + return AsyncItemPaged(get_next, extract_data) @distributed_trace_async - async def update_version( - self, - name: str, - version: str, - evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], - **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + async def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreResult: + """Delete a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to delete. Required. :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Is one of the following types: EvaluatorVersion, - JSON, IO[bytes] Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: DeleteMemoryStoreResult. The DeleteMemoryStoreResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DeleteMemoryStoreResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4609,26 +5325,15 @@ async def update_version( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - - content_type = content_type or "application/json" - _content = None - if isinstance(evaluator_version, (IOBase, bytes)): - _content = evaluator_version - else: - _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.DeleteMemoryStoreResult] = kwargs.pop("cls", None) - _request = build_beta_evaluators_update_version_request( + _request = build_beta_memory_stores_delete_request( name=name, - version=version, foundry_features=_foundry_features, - content_type=content_type, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -4652,12 +5357,16 @@ async def update_version( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluatorVersion, response.json()) + deserialized = _deserialize(_models.DeleteMemoryStoreResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -4665,107 +5374,60 @@ async def update_version( return deserialized # type: ignore @overload - async def pending_upload( + async def _search_memories( self, name: str, - version: str, - pending_upload_request: _models.PendingUploadRequest, *, + scope: str, content_type: str = "application/json", + items: Optional[List[dict[str, Any]]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def pending_upload( - self, - name: str, - version: str, - pending_upload_request: JSON, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def pending_upload( - self, - name: str, - version: str, - pending_upload_request: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ + ) -> _models.MemoryStoreSearchResult: ... + @overload + async def _search_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: ... + @overload + async def _search_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: ... @distributed_trace_async - async def pending_upload( + async def _search_memories( self, name: str, - version: str, - pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[List[dict[str, Any]]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. - :param name: The name of the resource. Required. + :param name: The name of the memory store to search. Required. :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Is one of the following - types: PendingUploadRequest, JSON, IO[bytes] Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or - IO[bytes] - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: Items for which to search for relevant memories. Default value is None. + :paramtype items: list[dict[str, any]] + :keyword previous_search_id: The unique ID of the previous search request, enabling incremental + memory search from where the last operation left off. Default value is None. + :paramtype previous_search_id: str + :keyword options: Memory search options. Default value is None. + :paramtype options: ~azure.ai.projects.models.MemorySearchOptions + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4779,18 +5441,27 @@ async def pending_upload( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) + cls: ClsType[_models.MemoryStoreSearchResult] = kwargs.pop("cls", None) + if body is _Unset: + if scope is _Unset: + raise TypeError("missing required argument: scope") + body = { + "items": items, + "options": options, + "previous_search_id": previous_search_id, + "scope": scope, + } + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(pending_upload_request, (IOBase, bytes)): - _content = pending_upload_request + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluators_pending_upload_request( + _request = build_beta_memory_stores_search_memories_request( name=name, - version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -4818,120 +5489,35 @@ async def pending_upload( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.PendingUploadResponse, response.json()) + deserialized = _deserialize(_models.MemoryStoreSearchResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - async def get_credentials( - self, - name: str, - version: str, - credential_request: _models.EvaluatorCredentialRequest, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Required. - :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def get_credentials( - self, - name: str, - version: str, - credential_request: JSON, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Required. - :type credential_request: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def get_credentials( + async def _update_memories_initial( self, name: str, - version: str, - credential_request: IO[bytes], + body: Union[JSON, IO[bytes]] = _Unset, *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Required. - :type credential_request: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - async def get_credentials( - self, - name: str, - version: str, - credential_request: Union[_models.EvaluatorCredentialRequest, JSON, IO[bytes]], + scope: str = _Unset, + items: Optional[List[dict[str, Any]]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Is one of the following types: - EvaluatorCredentialRequest, JSON, IO[bytes] Required. - :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest or JSON or - IO[bytes] - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) -> AsyncIterator[bytes]: + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4945,18 +5531,27 @@ async def get_credentials( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + if body is _Unset: + if scope is _Unset: + raise TypeError("missing required argument: scope") + body = { + "items": items, + "previous_update_id": previous_update_id, + "scope": scope, + "update_delay": update_delay, + } + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(credential_request, (IOBase, bytes)): - _content = credential_request + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(credential_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluators_get_credentials_request( + _request = build_beta_memory_stores_update_memories_request( name=name, - version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -4970,114 +5565,237 @@ async def get_credentials( _request.url = self._client.format_url(_request.url, **path_format_arguments) _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) + _stream = True pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.DatasetCredential, response.json()) + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return cls(pipeline_response, deserialized, response_headers) # type: ignore return deserialized # type: ignore + @overload + async def _begin_update_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[List[dict[str, Any]]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any + ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: ... + @overload + async def _begin_update_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: ... + @overload + async def _begin_update_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: ... -class BetaInsightsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + @distributed_trace_async + async def _begin_update_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[List[dict[str, Any]]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any + ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: + """Update memory store with conversation memories. - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`insights` attribute. - """ + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: Conversation items to be stored in memory. Default value is None. + :paramtype items: list[dict[str, any]] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of AsyncLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.MemoryStoreUpdateCompletedResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + ) + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._update_memories_initial( + name=name, + body=body, + foundry_features=_foundry_features, + scope=scope, + items=items, + previous_update_id=previous_update_id, + update_delay=update_delay, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.MemoryStoreUpdateCompletedResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) @overload - async def generate( - self, insight: _models.Insight, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Insight: - """Generate Insights. + async def delete_scope( + self, name: str, *, scope: str, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Required. - :type insight: ~azure.ai.projects.models.Insight + :param name: The name of the memory store. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories to delete, such as a + user ID. Required. + :paramtype scope: str :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def generate( - self, insight: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Insight: - """Generate Insights. + async def delete_scope( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Required. - :type insight: JSON + :param name: The name of the memory store. Required. + :type name: str + :param body: Required. + :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def generate( - self, insight: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Insight: - """Generate Insights. + async def delete_scope( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Required. - :type insight: IO[bytes] + :param name: The name of the memory store. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: Any) -> _models.Insight: - """Generate Insights. + async def delete_scope( + self, name: str, body: Union[JSON, IO[bytes]] = _Unset, *, scope: str = _Unset, **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Is one of the following types: Insight, JSON, IO[bytes] Required. - :type insight: ~azure.ai.projects.models.Insight or JSON or IO[bytes] - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :param name: The name of the memory store. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories to delete, such as a + user ID. Required. + :paramtype scope: str + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -5091,16 +5809,22 @@ async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwa _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.Insight] = kwargs.pop("cls", None) + cls: ClsType[_models.MemoryStoreDeleteScopeResult] = kwargs.pop("cls", None) + if body is _Unset: + if scope is _Unset: + raise TypeError("missing required argument: scope") + body = {"scope": scope} + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(insight, (IOBase, bytes)): - _content = insight + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(insight, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_insights_generate_request( + _request = build_beta_memory_stores_delete_scope_request( + name=name, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -5121,42 +5845,59 @@ async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwa response = pipeline_response.http_response - if response.status_code not in [201]: + if response.status_code not in [200]: if _stream: try: await response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Insight, response.json()) + deserialized = _deserialize(_models.MemoryStoreDeleteScopeResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + +class BetaRedTeamsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`red_teams` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @distributed_trace_async - async def get( - self, insight_id: str, *, include_coordinates: Optional[bool] = None, **kwargs: Any - ) -> _models.Insight: - """Get a specific insight by Id. + async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: + """Get a redteam by name. - :param insight_id: The unique identifier for the insights report. Required. - :type insight_id: str - :keyword include_coordinates: Whether to include coordinates for visualization in the response. - Defaults to false. Default value is None. - :paramtype include_coordinates: bool - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :param name: Identifier of the red team run. Required. + :type name: str + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -5169,12 +5910,11 @@ async def get( _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Insight] = kwargs.pop("cls", None) + cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) - _request = build_beta_insights_get_request( - insight_id=insight_id, + _request = build_beta_red_teams_get_request( + name=name, foundry_features=_foundry_features, - include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5204,7 +5944,7 @@ async def get( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Insight, response.json()) + deserialized = _deserialize(_models.RedTeam, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -5212,41 +5952,20 @@ async def get( return deserialized # type: ignore @distributed_trace - def list( - self, - *, - type: Optional[Union[str, _models.InsightType]] = None, - eval_id: Optional[str] = None, - run_id: Optional[str] = None, - agent_name: Optional[str] = None, - include_coordinates: Optional[bool] = None, - **kwargs: Any - ) -> AsyncItemPaged["_models.Insight"]: - """List all insights in reverse chronological order (newest first). + def list(self, **kwargs: Any) -> AsyncItemPaged["_models.RedTeam"]: + """List a redteam by name. - :keyword type: Filter by the type of analysis. Known values are: "EvaluationRunClusterInsight", - "AgentClusterInsight", and "EvaluationComparison". Default value is None. - :paramtype type: str or ~azure.ai.projects.models.InsightType - :keyword eval_id: Filter by the evaluation ID. Default value is None. - :paramtype eval_id: str - :keyword run_id: Filter by the evaluation run ID. Default value is None. - :paramtype run_id: str - :keyword agent_name: Filter by the agent name. Default value is None. - :paramtype agent_name: str - :keyword include_coordinates: Whether to include coordinates for visualization in the response. - Defaults to false. Default value is None. - :paramtype include_coordinates: bool - :return: An iterator like instance of Insight - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Insight] + :return: An iterator like instance of RedTeam + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.RedTeam] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Insight]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.RedTeam]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -5259,13 +5978,8 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_beta_insights_list_request( + _request = build_beta_red_teams_list_request( foundry_features=_foundry_features, - type=type, - eval_id=eval_id, - run_id=run_id, - agent_name=agent_name, - include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5305,7 +6019,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Insight], + List[_models.RedTeam], deserialized.get("value", []), ) if cls: @@ -5329,116 +6043,65 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) - -class BetaMemoryStoresOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`memory_stores` attribute. - """ - - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - @overload async def create( - self, - *, - name: str, - definition: _models.MemoryStoreDefinition, - content_type: str = "application/json", - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + self, red_team: _models.RedTeam, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.RedTeam: + """Creates a redteam run. - :keyword name: The name of the memory store. Required. - :paramtype name: str - :keyword definition: The memory store definition. Required. - :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition + :param red_team: Redteam to be run. Required. + :type red_team: ~azure.ai.projects.models.RedTeam :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + async def create(self, red_team: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.RedTeam: + """Creates a redteam run. - :param body: Required. - :type body: JSON + :param red_team: Redteam to be run. Required. + :type red_team: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ @overload async def create( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + self, red_team: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.RedTeam: + """Creates a redteam run. - :param body: Required. - :type body: IO[bytes] + :param red_team: Redteam to be run. Required. + :type red_team: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create( - self, - body: Union[JSON, IO[bytes]] = _Unset, - *, - name: str = _Unset, - definition: _models.MemoryStoreDefinition = _Unset, - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + async def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwargs: Any) -> _models.RedTeam: + """Creates a redteam run. - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword name: The name of the memory store. Required. - :paramtype name: str - :keyword definition: The memory store definition. Required. - :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :param red_team: Redteam to be run. Is one of the following types: RedTeam, JSON, IO[bytes] + Required. + :type red_team: ~azure.ai.projects.models.RedTeam or JSON or IO[bytes] + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -5452,23 +6115,16 @@ async def create( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) - if body is _Unset: - if name is _Unset: - raise TypeError("missing required argument: name") - if definition is _Unset: - raise TypeError("missing required argument: definition") - body = {"definition": definition, "description": description, "metadata": metadata, "name": name} - body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(red_team, (IOBase, bytes)): + _content = red_team else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(red_team, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_memory_stores_create_request( + _request = build_beta_red_teams_create_request( foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -5489,7 +6145,7 @@ async def create( response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [201]: if _stream: try: await response.read() # Load the body in memory and close the socket @@ -5505,185 +6161,43 @@ async def create( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) + deserialized = _deserialize(_models.RedTeam, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - async def update( - self, - name: str, - *, - content_type: str = "application/json", - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def update( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def update( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - async def update( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - - if body is _Unset: - body = {"description": description, "metadata": metadata} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_memory_stores_update_request( - name=name, - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) +class BetaSchedulesOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`schedules` attribute. + """ - return deserialized # type: ignore + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace_async - async def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: - """Retrieve a memory store. + async def delete(self, schedule_id: str, **kwargs: Any) -> None: + """Delete a schedule. - :param name: The name of the memory store to retrieve. Required. - :type name: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :return: None + :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -5696,670 +6210,12 @@ async def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - - _request = build_beta_memory_stores_get_request( - name=name, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - @distributed_trace - def list( - self, - *, - limit: Optional[int] = None, - order: Optional[Union[str, _models.PageOrder]] = None, - before: Optional[str] = None, - **kwargs: Any - ) -> AsyncItemPaged["_models.MemoryStoreDetails"]: - """List all memory stores. - - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the - default is 20. Default value is None. - :paramtype limit: int - :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for - ascending order and``desc`` - for descending order. Known values are: "asc" and "desc". Default value is None. - :paramtype order: str or ~azure.ai.projects.models.PageOrder - :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your - place in the list. - For instance, if you make a list request and receive 100 objects, ending with obj_foo, your - subsequent call can include before=obj_foo in order to fetch the previous page of the list. - Default value is None. - :paramtype before: str - :return: An iterator like instance of MemoryStoreDetails - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.MemoryStoreDetails] - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[List[_models.MemoryStoreDetails]] = kwargs.pop("cls", None) - - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - def prepare_request(_continuation_token=None): - - _request = build_beta_memory_stores_list_request( - foundry_features=_foundry_features, - limit=limit, - order=order, - after=_continuation_token, - before=before, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - return _request - - async def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.MemoryStoreDetails], - deserialized.get("data", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("last_id") or None, AsyncList(list_of_elem) - - async def get_next(_continuation_token=None): - _request = prepare_request(_continuation_token) - - _stream = False - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - return pipeline_response - - return AsyncItemPaged(get_next, extract_data) - - @distributed_trace_async - async def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreResult: - """Delete a memory store. - - :param name: The name of the memory store to delete. Required. - :type name: str - :return: DeleteMemoryStoreResult. The DeleteMemoryStoreResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DeleteMemoryStoreResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[_models.DeleteMemoryStoreResult] = kwargs.pop("cls", None) - - _request = build_beta_memory_stores_delete_request( - name=name, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.DeleteMemoryStoreResult, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - @overload - async def _search_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[List[dict[str, Any]]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any - ) -> _models.MemoryStoreSearchResult: ... - @overload - async def _search_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: ... - @overload - async def _search_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: ... - - @distributed_trace_async - async def _search_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[List[dict[str, Any]]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: Items for which to search for relevant memories. Default value is None. - :paramtype items: list[dict[str, any]] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreSearchResult] = kwargs.pop("cls", None) - - if body is _Unset: - if scope is _Unset: - raise TypeError("missing required argument: scope") - body = { - "items": items, - "options": options, - "previous_search_id": previous_search_id, - "scope": scope, - } - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_memory_stores_search_memories_request( - name=name, - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreSearchResult, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - async def _update_memories_initial( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[List[dict[str, Any]]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any - ) -> AsyncIterator[bytes]: - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) - - if body is _Unset: - if scope is _Unset: - raise TypeError("missing required argument: scope") - body = { - "items": items, - "previous_update_id": previous_update_id, - "scope": scope, - "update_delay": update_delay, - } - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_memory_stores_update_memories_request( - name=name, - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = True - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [202]: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - response_headers = {} - response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) - - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - - return deserialized # type: ignore - - @overload - async def _begin_update_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[List[dict[str, Any]]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any - ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: ... - @overload - async def _begin_update_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: ... - @overload - async def _begin_update_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: ... - - @distributed_trace_async - async def _begin_update_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[List[dict[str, Any]]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any - ) -> AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: Conversation items to be stored in memory. Default value is None. - :paramtype items: list[dict[str, any]] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :return: An instance of AsyncLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.MemoryStoreUpdateCompletedResult] - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) - lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) - cont_token: Optional[str] = kwargs.pop("continuation_token", None) - if cont_token is None: - raw_result = await self._update_memories_initial( - name=name, - body=body, - foundry_features=_foundry_features, - scope=scope, - items=items, - previous_update_id=previous_update_id, - update_delay=update_delay, - content_type=content_type, - cls=lambda x, y, z: x, - headers=_headers, - params=_params, - **kwargs - ) - await raw_result.http_response.read() # type: ignore - kwargs.pop("error_map", None) - - def get_long_running_output(pipeline_response): - response_headers = {} - response = pipeline_response.http_response - response_headers["Operation-Location"] = self._deserialize( - "str", response.headers.get("Operation-Location") - ) - - deserialized = _deserialize(_models.MemoryStoreUpdateCompletedResult, response.json().get("result", {})) - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - return deserialized - - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - - if polling is True: - polling_method: AsyncPollingMethod = cast( - AsyncPollingMethod, - AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), - ) - elif polling is False: - polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) - else: - polling_method = polling - if cont_token: - return AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult].from_continuation_token( - polling_method=polling_method, - continuation_token=cont_token, - client=self._client, - deserialization_callback=get_long_running_output, - ) - return AsyncLROPoller[_models.MemoryStoreUpdateCompletedResult]( - self._client, raw_result, get_long_running_output, polling_method # type: ignore - ) - - @overload - async def delete_scope( - self, name: str, *, scope: str, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. - - :param name: The name of the memory store. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories to delete, such as a - user ID. Required. - :paramtype scope: str - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def delete_scope( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. - - :param name: The name of the memory store. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def delete_scope( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. - - :param name: The name of the memory store. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - async def delete_scope( - self, name: str, body: Union[JSON, IO[bytes]] = _Unset, *, scope: str = _Unset, **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. - - :param name: The name of the memory store. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories to delete, such as a - user ID. Required. - :paramtype scope: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreDeleteScopeResult] = kwargs.pop("cls", None) - - if body is _Unset: - if scope is _Unset: - raise TypeError("missing required argument: scope") - body = {"scope": scope} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_beta_memory_stores_delete_scope_request( - name=name, + _request = build_beta_schedules_delete_request( + schedule_id=schedule_id, foundry_features=_foundry_features, - content_type=content_type, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -6368,67 +6224,32 @@ async def delete_scope( } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) + _stream = False pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [200]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass + if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreDeleteScopeResult, response.json()) + raise HttpResponseError(response=response) if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - -class BetaRedTeamsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`red_teams` attribute. - """ - - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + return cls(pipeline_response, None, {}) # type: ignore @distributed_trace_async - async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: - """Get a redteam by name. + async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: + """Get a schedule by id. - :param name: Identifier of the red team run. Required. - :type name: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6441,10 +6262,10 @@ async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) + cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) - _request = build_beta_red_teams_get_request( - name=name, + _request = build_beta_schedules_get_request( + schedule_id=schedule_id, foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, @@ -6475,7 +6296,7 @@ async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.RedTeam, response.json()) + deserialized = _deserialize(_models.Schedule, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -6483,20 +6304,31 @@ async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: return deserialized # type: ignore @distributed_trace - def list(self, **kwargs: Any) -> AsyncItemPaged["_models.RedTeam"]: - """List a redteam by name. + def list( + self, + *, + type: Optional[Union[str, _models.ScheduleTaskType]] = None, + enabled: Optional[bool] = None, + **kwargs: Any + ) -> AsyncItemPaged["_models.Schedule"]: + """List all schedules. - :return: An iterator like instance of RedTeam - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.RedTeam] + :keyword type: Filter by the type of schedule. Known values are: "Evaluation" and "Insight". + Default value is None. + :paramtype type: str or ~azure.ai.projects.models.ScheduleTaskType + :keyword enabled: Filter by the enabled status. Default value is None. + :paramtype enabled: bool + :return: An iterator like instance of Schedule + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Schedule] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.RedTeam]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Schedule]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6509,8 +6341,10 @@ def list(self, **kwargs: Any) -> AsyncItemPaged["_models.RedTeam"]: def prepare_request(next_link=None): if not next_link: - _request = build_beta_red_teams_list_request( + _request = build_beta_schedules_list_request( foundry_features=_foundry_features, + type=type, + enabled=enabled, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6550,7 +6384,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.RedTeam], + List[_models.Schedule], deserialized.get("value", []), ) if cls: @@ -6575,156 +6409,72 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) @overload - async def create( - self, red_team: _models.RedTeam, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.RedTeam: - """Creates a redteam run. + async def create_or_update( + self, schedule_id: str, schedule: _models.Schedule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. - :param red_team: Redteam to be run. Required. - :type red_team: ~azure.ai.projects.models.RedTeam + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :param schedule: The resource instance. Required. + :type schedule: ~azure.ai.projects.models.Schedule :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create(self, red_team: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.RedTeam: - """Creates a redteam run. + async def create_or_update( + self, schedule_id: str, schedule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. - :param red_team: Redteam to be run. Required. - :type red_team: JSON + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :param schedule: The resource instance. Required. + :type schedule: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create( - self, red_team: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.RedTeam: - """Creates a redteam run. - - :param red_team: Redteam to be run. Required. - :type red_team: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - async def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwargs: Any) -> _models.RedTeam: - """Creates a redteam run. - - :param red_team: Redteam to be run. Is one of the following types: RedTeam, JSON, IO[bytes] - Required. - :type red_team: ~azure.ai.projects.models.RedTeam or JSON or IO[bytes] - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) - - content_type = content_type or "application/json" - _content = None - if isinstance(red_team, (IOBase, bytes)): - _content = red_team - else: - _content = json.dumps(red_team, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_red_teams_create_request( - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [201]: - if _stream: - try: - await response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.RedTeam, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - -class BetaSchedulesOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`schedules` attribute. - """ + async def create_or_update( + self, schedule_id: str, schedule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :param schedule: The resource instance. Required. + :type schedule: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace_async - async def delete(self, schedule_id: str, **kwargs: Any) -> None: - """Delete a schedule. + async def create_or_update( + self, schedule_id: str, schedule: Union[_models.Schedule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. :param schedule_id: Identifier of the schedule. Required. :type schedule_id: str - :return: None - :rtype: None + :param schedule: The resource instance. Is one of the following types: Schedule, JSON, + IO[bytes] Required. + :type schedule: ~azure.ai.projects.models.Schedule or JSON or IO[bytes] + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( @@ -6738,15 +6488,25 @@ async def delete(self, schedule_id: str, **kwargs: Any) -> None: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[None] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) - _request = build_beta_schedules_delete_request( + content_type = content_type or "application/json" + _content = None + if isinstance(schedule, (IOBase, bytes)): + _content = schedule + else: + _content = json.dumps(schedule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_schedules_create_or_update_request( schedule_id=schedule_id, foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -6755,28 +6515,43 @@ async def delete(self, schedule_id: str, **kwargs: Any) -> None: } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [204]: + if response.status_code not in [200, 201]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Schedule, response.json()) + if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @distributed_trace_async - async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: - """Get a schedule by id. + async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.ScheduleRun: + """Get a schedule run by id. - :param schedule_id: Identifier of the schedule. Required. + :param schedule_id: The unique identifier of the schedule. Required. :type schedule_id: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :param run_id: The unique identifier of the schedule run. Required. + :type run_id: str + :return: ScheduleRun. The ScheduleRun is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ScheduleRun :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( @@ -6793,10 +6568,11 @@ async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) + cls: ClsType[_models.ScheduleRun] = kwargs.pop("cls", None) - _request = build_beta_schedules_get_request( + _request = build_beta_schedules_get_run_request( schedule_id=schedule_id, + run_id=run_id, foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, @@ -6822,12 +6598,16 @@ async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Schedule, response.json()) + deserialized = _deserialize(_models.ScheduleRun, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -6835,22 +6615,25 @@ async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: return deserialized # type: ignore @distributed_trace - def list( + def list_runs( self, + schedule_id: str, *, type: Optional[Union[str, _models.ScheduleTaskType]] = None, enabled: Optional[bool] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.Schedule"]: - """List all schedules. + ) -> AsyncItemPaged["_models.ScheduleRun"]: + """List all schedule runs. + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str :keyword type: Filter by the type of schedule. Known values are: "Evaluation" and "Insight". Default value is None. :paramtype type: str or ~azure.ai.projects.models.ScheduleTaskType :keyword enabled: Filter by the enabled status. Default value is None. :paramtype enabled: bool - :return: An iterator like instance of Schedule - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Schedule] + :return: An iterator like instance of ScheduleRun + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.ScheduleRun] :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( @@ -6859,7 +6642,7 @@ def list( _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Schedule]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.ScheduleRun]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6872,7 +6655,8 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_beta_schedules_list_request( + _request = build_beta_schedules_list_runs_request( + schedule_id=schedule_id, foundry_features=_foundry_features, type=type, enabled=enabled, @@ -6915,7 +6699,7 @@ def prepare_request(next_link=None): async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Schedule], + List[_models.ScheduleRun], deserialized.get("value", []), ) if cls: @@ -6939,77 +6723,285 @@ async def get_next(next_link=None): return AsyncItemPaged(get_next, extract_data) + +class BetaToolsetsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`toolsets` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @overload - async def create_or_update( - self, schedule_id: str, schedule: _models.Schedule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + async def create( + self, + *, + name: str, + tools: List[_models.Tool], + content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :keyword name: The name of the toolset. Required. + :paramtype name: str + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def create( + self, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def create( + self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def create( + self, + body: Union[JSON, IO[bytes]] = _Unset, + *, + name: str = _Unset, + tools: List[_models.Tool] = _Unset, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword name: The name of the toolset. Required. + :paramtype name: str + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.ToolsetObject] = kwargs.pop("cls", None) + + if body is _Unset: + if name is _Unset: + raise TypeError("missing required argument: name") + if tools is _Unset: + raise TypeError("missing required argument: tools") + body = {"description": description, "metadata": metadata, "name": name, "tools": tools} + body = {k: v for k, v in body.items() if v is not None} + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_toolsets_create_request( + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.ToolsetObject, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @overload + async def update( + self, + tool_set_name: str, + *, + tools: List[_models.Tool], + content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Required. - :type schedule: ~azure.ai.projects.models.Schedule + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_or_update( - self, schedule_id: str, schedule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + async def update( + self, tool_set_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Required. - :type schedule: JSON + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :param body: Required. + :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ @overload - async def create_or_update( - self, schedule_id: str, schedule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + async def update( + self, tool_set_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Required. - :type schedule: IO[bytes] + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :param body: Required. + :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace_async - async def create_or_update( - self, schedule_id: str, schedule: Union[_models.Schedule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + async def update( + self, + tool_set_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + tools: List[_models.Tool] = _Unset, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Is one of the following types: Schedule, JSON, - IO[bytes] Required. - :type schedule: ~azure.ai.projects.models.Schedule or JSON or IO[bytes] - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -7023,17 +7015,22 @@ async def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) + cls: ClsType[_models.ToolsetObject] = kwargs.pop("cls", None) + if body is _Unset: + if tools is _Unset: + raise TypeError("missing required argument: tools") + body = {"description": description, "metadata": metadata, "tools": tools} + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(schedule, (IOBase, bytes)): - _content = schedule + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(schedule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_schedules_create_or_update_request( - schedule_id=schedule_id, + _request = build_beta_toolsets_update_request( + tool_set_name=tool_set_name, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -7054,19 +7051,23 @@ async def create_or_update( response = pipeline_response.http_response - if response.status_code not in [200, 201]: + if response.status_code not in [200]: if _stream: try: await response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Schedule, response.json()) + deserialized = _deserialize(_models.ToolsetObject, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -7074,19 +7075,17 @@ async def create_or_update( return deserialized # type: ignore @distributed_trace_async - async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.ScheduleRun: - """Get a schedule run by id. + async def get(self, tool_set_name: str, **kwargs: Any) -> _models.ToolsetObject: + """Retrieve a toolset. - :param schedule_id: The unique identifier of the schedule. Required. - :type schedule_id: str - :param run_id: The unique identifier of the schedule run. Required. - :type run_id: str - :return: ScheduleRun. The ScheduleRun is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.ScheduleRun + :param tool_set_name: The name of the toolset to retrieve. Required. + :type tool_set_name: str + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -7099,11 +7098,10 @@ async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.ScheduleRun] = kwargs.pop("cls", None) + cls: ClsType[_models.ToolsetObject] = kwargs.pop("cls", None) - _request = build_beta_schedules_get_run_request( - schedule_id=schedule_id, - run_id=run_id, + _request = build_beta_toolsets_get_request( + tool_set_name=tool_set_name, foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, @@ -7138,7 +7136,7 @@ async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.ScheduleRun, response.json()) + deserialized = _deserialize(_models.ToolsetObject, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -7146,34 +7144,41 @@ async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models return deserialized # type: ignore @distributed_trace - def list_runs( + def list( self, - schedule_id: str, *, - type: Optional[Union[str, _models.ScheduleTaskType]] = None, - enabled: Optional[bool] = None, + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + before: Optional[str] = None, **kwargs: Any - ) -> AsyncItemPaged["_models.ScheduleRun"]: - """List all schedule runs. + ) -> AsyncItemPaged["_models.ToolsetObject"]: + """List all toolsets. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :keyword type: Filter by the type of schedule. Known values are: "Evaluation" and "Insight". + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the + default is 20. Default value is None. + :paramtype limit: int + :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for + ascending order and``desc`` + for descending order. Known values are: "asc" and "desc". Default value is None. + :paramtype order: str or ~azure.ai.projects.models.PageOrder + :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your + place in the list. + For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page of the list. Default value is None. - :paramtype type: str or ~azure.ai.projects.models.ScheduleTaskType - :keyword enabled: Filter by the enabled status. Default value is None. - :paramtype enabled: bool - :return: An iterator like instance of ScheduleRun - :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.ScheduleRun] + :paramtype before: str + :return: An iterator like instance of ToolsetObject + :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.ToolsetObject] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.ScheduleRun]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.ToolsetObject]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -7183,62 +7188,36 @@ def list_runs( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(next_link=None): - if not next_link: - - _request = build_beta_schedules_list_runs_request( - schedule_id=schedule_id, - foundry_features=_foundry_features, - type=type, - enabled=enabled, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + def prepare_request(_continuation_token=None): + _request = build_beta_toolsets_list_request( + foundry_features=_foundry_features, + limit=limit, + order=order, + after=_continuation_token, + before=before, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) return _request async def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.ScheduleRun], - deserialized.get("value", []), + List[_models.ToolsetObject], + deserialized.get("data", []), ) if cls: list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, AsyncList(list_of_elem) + return deserialized.get("last_id") or None, AsyncList(list_of_elem) - async def get_next(next_link=None): - _request = prepare_request(next_link) + async def get_next(_continuation_token=None): + _request = prepare_request(_continuation_token) _stream = False pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access @@ -7248,8 +7227,81 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) return pipeline_response return AsyncItemPaged(get_next, extract_data) + + @distributed_trace_async + async def delete(self, tool_set_name: str, **kwargs: Any) -> _models.DeleteToolsetResponse: + """Delete a toolset. + + :param tool_set_name: The name of the toolset to delete. Required. + :type tool_set_name: str + :return: DeleteToolsetResponse. The DeleteToolsetResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DeleteToolsetResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.DeleteToolsetResponse] = kwargs.pop("cls", None) + + _request = build_beta_toolsets_delete_request( + tool_set_name=tool_set_name, + foundry_features=_foundry_features, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.DeleteToolsetResponse, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py index 110c948952f0..e93d0b108a9d 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/__init__.py @@ -84,6 +84,7 @@ DeleteAgentResponse, DeleteAgentVersionResponse, DeleteMemoryStoreResult, + DeleteToolsetResponse, Deployment, EmbeddingConfiguration, EntraIDCredentials, @@ -217,6 +218,7 @@ ToolChoiceWebSearchPreview20250311, ToolDescription, ToolProjectConnection, + ToolsetObject, Trigger, UserProfileMemoryItem, WebSearchApproximateLocation, @@ -225,6 +227,8 @@ WebSearchTool, WebSearchToolFilters, WeeklyRecurrenceSchedule, + WorkIQPreviewTool, + WorkIQPreviewToolParameters, WorkflowAgentDefinition, ) @@ -277,6 +281,7 @@ TextResponseFormatConfigurationType, ToolChoiceParamType, ToolType, + ToolsetObjectType, TreatmentEffectType, TriggerType, ) @@ -355,6 +360,7 @@ "DeleteAgentResponse", "DeleteAgentVersionResponse", "DeleteMemoryStoreResult", + "DeleteToolsetResponse", "Deployment", "EmbeddingConfiguration", "EntraIDCredentials", @@ -488,6 +494,7 @@ "ToolChoiceWebSearchPreview20250311", "ToolDescription", "ToolProjectConnection", + "ToolsetObject", "Trigger", "UserProfileMemoryItem", "WebSearchApproximateLocation", @@ -496,6 +503,8 @@ "WebSearchTool", "WebSearchToolFilters", "WeeklyRecurrenceSchedule", + "WorkIQPreviewTool", + "WorkIQPreviewToolParameters", "WorkflowAgentDefinition", "AgentKind", "AgentObjectType", @@ -545,6 +554,7 @@ "TextResponseFormatConfigurationType", "ToolChoiceParamType", "ToolType", + "ToolsetObjectType", "TreatmentEffectType", "TriggerType", ] diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index ae6e559371ff..923dd800ef60 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -371,6 +371,8 @@ class _FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): """INSIGHTS_V1_PREVIEW.""" MEMORY_STORES_V1_PREVIEW = "MemoryStores=V1Preview" """MEMORY_STORES_V1_PREVIEW.""" + TOOLSET_V1_PREVIEW = "Toolsets=V1Preview" + """TOOLSET_V1_PREVIEW.""" class FunctionShellToolParamEnvironmentType(str, Enum, metaclass=CaseInsensitiveEnumMeta): @@ -670,6 +672,15 @@ class ToolChoiceParamType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """CODE_INTERPRETER.""" +class ToolsetObjectType(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Type of ToolsetObjectType.""" + + TOOLSET = "toolset" + """TOOLSET.""" + TOOLSET_DELETED = "toolset.deleted" + """TOOLSET_DELETED.""" + + class ToolType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Type of ToolType.""" @@ -709,6 +720,8 @@ class ToolType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """SHAREPOINT_GROUNDING_PREVIEW.""" MEMORY_SEARCH_PREVIEW = "memory_search_preview" """MEMORY_SEARCH_PREVIEW.""" + WORK_IQ_PREVIEW = "work_iq_preview" + """WORK_IQ_PREVIEW.""" AZURE_AI_SEARCH = "azure_ai_search" """AZURE_AI_SEARCH.""" AZURE_FUNCTION = "azure_function" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index bae28a926cec..f009264826d7 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -38,6 +38,7 @@ TextResponseFormatConfigurationType, ToolChoiceParamType, ToolType, + ToolsetObjectType, TriggerType, ) @@ -54,14 +55,14 @@ class Tool(_Model): CaptureStructuredOutputsTool, CodeInterpreterTool, ComputerUsePreviewTool, CustomToolParam, MicrosoftFabricPreviewTool, FileSearchTool, FunctionTool, ImageGenTool, LocalShellToolParam, MCPTool, MemorySearchPreviewTool, OpenApiTool, SharepointPreviewTool, FunctionShellToolParam, - WebSearchTool, WebSearchPreviewTool + WebSearchTool, WebSearchPreviewTool, WorkIQPreviewTool :ivar type: Required. Known values are: "function", "file_search", "computer_use_preview", "web_search", "mcp", "code_interpreter", "image_generation", "local_shell", "shell", "custom", "web_search_preview", "apply_patch", "a2a_preview", "bing_custom_search_preview", "browser_automation_preview", "fabric_dataagent_preview", "sharepoint_grounding_preview", - "memory_search_preview", "azure_ai_search", "azure_function", "bing_grounding", - "capture_structured_outputs", and "openapi". + "memory_search_preview", "work_iq_preview", "azure_ai_search", "azure_function", + "bing_grounding", "capture_structured_outputs", and "openapi". :vartype type: str or ~azure.ai.projects.models.ToolType """ @@ -71,8 +72,9 @@ class Tool(_Model): \"web_search\", \"mcp\", \"code_interpreter\", \"image_generation\", \"local_shell\", \"shell\", \"custom\", \"web_search_preview\", \"apply_patch\", \"a2a_preview\", \"bing_custom_search_preview\", \"browser_automation_preview\", \"fabric_dataagent_preview\", - \"sharepoint_grounding_preview\", \"memory_search_preview\", \"azure_ai_search\", - \"azure_function\", \"bing_grounding\", \"capture_structured_outputs\", and \"openapi\".""" + \"sharepoint_grounding_preview\", \"memory_search_preview\", \"work_iq_preview\", + \"azure_ai_search\", \"azure_function\", \"bing_grounding\", \"capture_structured_outputs\", + and \"openapi\".""" @overload def __init__( @@ -97,6 +99,10 @@ class A2APreviewTool(Tool, discriminator="a2a_preview"): :ivar type: The type of the tool. Always ``"a2a_preview``. Required. A2A_PREVIEW. :vartype type: str or ~azure.ai.projects.models.A2A_PREVIEW + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar base_url: Base URL of the agent. :vartype base_url: str :ivar agent_card_path: The path to the agent card relative to the ``base_url``. If not @@ -110,6 +116,10 @@ class A2APreviewTool(Tool, discriminator="a2a_preview"): type: Literal[ToolType.A2A_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The type of the tool. Always ``\"a2a_preview``. Required. A2A_PREVIEW.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" base_url: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Base URL of the agent.""" agent_card_path: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -123,6 +133,8 @@ class A2APreviewTool(Tool, discriminator="a2a_preview"): def __init__( self, *, + name: Optional[str] = None, + description: Optional[str] = None, base_url: Optional[str] = None, agent_card_path: Optional[str] = None, project_connection_id: Optional[str] = None, @@ -614,6 +626,10 @@ class AISearchIndexResource(_Model): :vartype project_connection_id: str :ivar index_name: The name of an index in an IndexResource attached to this agent. :vartype index_name: str + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar query_type: Type of query in an AIIndexResource attached to this agent. Known values are: "simple", "semantic", "vector", "vector_simple_hybrid", and "vector_semantic_hybrid". :vartype query_type: str or ~azure.ai.projects.models.AzureAISearchQueryType @@ -630,6 +646,10 @@ class AISearchIndexResource(_Model): """An index connection ID in an IndexResource attached to this agent.""" index_name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """The name of an index in an IndexResource attached to this agent.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" query_type: Optional[Union[str, "_models.AzureAISearchQueryType"]] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -649,6 +669,8 @@ def __init__( *, project_connection_id: Optional[str] = None, index_name: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, query_type: Optional[Union[str, "_models.AzureAISearchQueryType"]] = None, top_k: Optional[int] = None, filter: Optional[str] = None, # pylint: disable=redefined-builtin @@ -1138,12 +1160,20 @@ class AzureAISearchTool(Tool, discriminator="azure_ai_search"): :ivar type: The object type, which is always 'azure_ai_search'. Required. AZURE_AI_SEARCH. :vartype type: str or ~azure.ai.projects.models.AZURE_AI_SEARCH + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar azure_ai_search: The azure ai search index resource. Required. :vartype azure_ai_search: ~azure.ai.projects.models.AzureAISearchToolResource """ type: Literal[ToolType.AZURE_AI_SEARCH] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The object type, which is always 'azure_ai_search'. Required. AZURE_AI_SEARCH.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" azure_ai_search: "_models.AzureAISearchToolResource" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1154,6 +1184,8 @@ def __init__( self, *, azure_ai_search: "_models.AzureAISearchToolResource", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1171,11 +1203,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class AzureAISearchToolResource(_Model): """A set of index resources used by the ``azure_ai_search`` tool. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar indexes: The indices attached to this agent. There can be a maximum of 1 index resource attached to the agent. Required. :vartype indexes: list[~azure.ai.projects.models.AISearchIndexResource] """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" indexes: list["_models.AISearchIndexResource"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1187,6 +1227,8 @@ def __init__( self, *, indexes: list["_models.AISearchIndexResource"], + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1469,6 +1511,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class BingCustomSearchConfiguration(_Model): """A bing custom search configuration. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connection_id: Project connection id for grounding with bing search. Required. :vartype project_connection_id: str :ivar instance_name: Name of the custom configuration instance given to config. Required. @@ -1484,6 +1530,10 @@ class BingCustomSearchConfiguration(_Model): :vartype freshness: str """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connection_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Project connection id for grounding with bing search. Required.""" instance_name: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -1504,6 +1554,8 @@ def __init__( *, project_connection_id: str, instance_name: str, + name: Optional[str] = None, + description: Optional[str] = None, market: Optional[str] = None, set_lang: Optional[str] = None, count: Optional[int] = None, @@ -1527,6 +1579,10 @@ class BingCustomSearchPreviewTool(Tool, discriminator="bing_custom_search_previe :ivar type: The object type, which is always 'bing_custom_search_preview'. Required. BING_CUSTOM_SEARCH_PREVIEW. :vartype type: str or ~azure.ai.projects.models.BING_CUSTOM_SEARCH_PREVIEW + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar bing_custom_search_preview: The bing custom search tool parameters. Required. :vartype bing_custom_search_preview: ~azure.ai.projects.models.BingCustomSearchToolParameters """ @@ -1534,6 +1590,10 @@ class BingCustomSearchPreviewTool(Tool, discriminator="bing_custom_search_previe type: Literal[ToolType.BING_CUSTOM_SEARCH_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The object type, which is always 'bing_custom_search_preview'. Required. BING_CUSTOM_SEARCH_PREVIEW.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" bing_custom_search_preview: "_models.BingCustomSearchToolParameters" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1544,6 +1604,8 @@ def __init__( self, *, bing_custom_search_preview: "_models.BingCustomSearchToolParameters", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1561,11 +1623,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class BingCustomSearchToolParameters(_Model): """The bing custom search tool parameters. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar search_configurations: The project connections attached to this tool. There can be a maximum of 1 connection resource attached to the tool. Required. :vartype search_configurations: list[~azure.ai.projects.models.BingCustomSearchConfiguration] """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" search_configurations: list["_models.BingCustomSearchConfiguration"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1577,6 +1647,8 @@ def __init__( self, *, search_configurations: list["_models.BingCustomSearchConfiguration"], + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1593,6 +1665,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class BingGroundingSearchConfiguration(_Model): """Search configuration for Bing Grounding. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connection_id: Project connection id for grounding with bing search. Required. :vartype project_connection_id: str :ivar market: The market where the results come from. @@ -1606,6 +1682,10 @@ class BingGroundingSearchConfiguration(_Model): :vartype freshness: str """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connection_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Project connection id for grounding with bing search. Required.""" market: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -1623,6 +1703,8 @@ def __init__( self, *, project_connection_id: str, + name: Optional[str] = None, + description: Optional[str] = None, market: Optional[str] = None, set_lang: Optional[str] = None, count: Optional[int] = None, @@ -1643,12 +1725,20 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class BingGroundingSearchToolParameters(_Model): """The bing grounding search tool parameters. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar search_configurations: The search configurations attached to this tool. There can be a maximum of 1 search configuration resource attached to the tool. Required. :vartype search_configurations: list[~azure.ai.projects.models.BingGroundingSearchConfiguration] """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" search_configurations: list["_models.BingGroundingSearchConfiguration"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1660,6 +1750,8 @@ def __init__( self, *, search_configurations: list["_models.BingGroundingSearchConfiguration"], + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1679,12 +1771,20 @@ class BingGroundingTool(Tool, discriminator="bing_grounding"): :ivar type: The object type, which is always 'bing_grounding'. Required. BING_GROUNDING. :vartype type: str or ~azure.ai.projects.models.BING_GROUNDING + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar bing_grounding: The bing grounding search tool parameters. Required. :vartype bing_grounding: ~azure.ai.projects.models.BingGroundingSearchToolParameters """ type: Literal[ToolType.BING_GROUNDING] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The object type, which is always 'bing_grounding'. Required. BING_GROUNDING.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" bing_grounding: "_models.BingGroundingSearchToolParameters" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1695,6 +1795,8 @@ def __init__( self, *, bing_grounding: "_models.BingGroundingSearchToolParameters", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1778,6 +1880,10 @@ class BrowserAutomationPreviewTool(Tool, discriminator="browser_automation_previ :ivar type: The object type, which is always 'browser_automation_preview'. Required. BROWSER_AUTOMATION_PREVIEW. :vartype type: str or ~azure.ai.projects.models.BROWSER_AUTOMATION_PREVIEW + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar browser_automation_preview: The Browser Automation Tool parameters. Required. :vartype browser_automation_preview: ~azure.ai.projects.models.BrowserAutomationToolParameters """ @@ -1785,6 +1891,10 @@ class BrowserAutomationPreviewTool(Tool, discriminator="browser_automation_previ type: Literal[ToolType.BROWSER_AUTOMATION_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The object type, which is always 'browser_automation_preview'. Required. BROWSER_AUTOMATION_PREVIEW.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" browser_automation_preview: "_models.BrowserAutomationToolParameters" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1795,6 +1905,8 @@ def __init__( self, *, browser_automation_preview: "_models.BrowserAutomationToolParameters", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1812,11 +1924,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class BrowserAutomationToolConnectionParameters(_Model): # pylint: disable=name-too-long """Definition of input parameters for the connection used by the Browser Automation Tool. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connection_id: The ID of the project connection to your Azure Playwright resource. Required. :vartype project_connection_id: str """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connection_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """The ID of the project connection to your Azure Playwright resource. Required.""" @@ -1825,6 +1945,8 @@ def __init__( self, *, project_connection_id: str, + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -1841,11 +1963,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class BrowserAutomationToolParameters(_Model): """Definition of input parameters for the Browser Automation Tool. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar connection: The project connection parameters associated with the Browser Automation Tool. Required. :vartype connection: ~azure.ai.projects.models.BrowserAutomationToolConnectionParameters """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" connection: "_models.BrowserAutomationToolConnectionParameters" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -1856,6 +1986,8 @@ def __init__( self, *, connection: "_models.BrowserAutomationToolConnectionParameters", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -2237,6 +2369,8 @@ class CodeBasedEvaluatorDefinition(EvaluatorDefinition, discriminator="code"): :vartype entry_point: str :ivar image_tag: The container image tag to use for evaluator code execution. :vartype image_tag: str + :ivar blob_uri: The blob URI for the evaluator storage. + :vartype blob_uri: str """ type: Literal[EvaluatorDefinitionType.CODE] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore @@ -2248,6 +2382,8 @@ class CodeBasedEvaluatorDefinition(EvaluatorDefinition, discriminator="code"): 'answer_length_evaluator.py').""" image_tag: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """The container image tag to use for evaluator code execution.""" + blob_uri: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The blob URI for the evaluator storage.""" @overload def __init__( @@ -2259,6 +2395,7 @@ def __init__( code_text: Optional[str] = None, entry_point: Optional[str] = None, image_tag: Optional[str] = None, + blob_uri: Optional[str] = None, ) -> None: ... @overload @@ -2279,6 +2416,10 @@ class CodeInterpreterTool(Tool, discriminator="code_interpreter"): :ivar type: The type of the code interpreter tool. Always ``code_interpreter``. Required. CODE_INTERPRETER. :vartype type: str or ~azure.ai.projects.models.CODE_INTERPRETER + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar container: The code interpreter container. Can be a container ID or an object that specifies uploaded file IDs to make available to your code, along with an optional ``memory_limit`` setting. If not provided, the service assumes auto. Is either a str type or a @@ -2288,6 +2429,10 @@ class CodeInterpreterTool(Tool, discriminator="code_interpreter"): type: Literal[ToolType.CODE_INTERPRETER] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The type of the code interpreter tool. Always ``code_interpreter``. Required. CODE_INTERPRETER.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" container: Optional[Union[str, "_models.AutoCodeInterpreterToolParam"]] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -2300,6 +2445,8 @@ class CodeInterpreterTool(Tool, discriminator="code_interpreter"): def __init__( self, *, + name: Optional[str] = None, + description: Optional[str] = None, container: Optional[Union[str, "_models.AutoCodeInterpreterToolParam"]] = None, ) -> None: ... @@ -3462,6 +3609,46 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) +class DeleteToolsetResponse(_Model): + """Response returned when a toolset is deleted. + + :ivar object: The object type. Always 'toolset.deleted'. Required. TOOLSET_DELETED. + :vartype object: str or ~azure.ai.projects.models.TOOLSET_DELETED + :ivar name: The name of the toolset. Required. + :vartype name: str + :ivar deleted: Whether the toolset was successfully deleted. Required. + :vartype deleted: bool + """ + + object: Literal[ToolsetObjectType.TOOLSET_DELETED] = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + """The object type. Always 'toolset.deleted'. Required. TOOLSET_DELETED.""" + name: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The name of the toolset. Required.""" + deleted: bool = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Whether the toolset was successfully deleted. Required.""" + + @overload + def __init__( + self, + *, + object: Literal[ToolsetObjectType.TOOLSET_DELETED], + name: str, + deleted: bool, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + class Deployment(_Model): """Model Deployment Definition. @@ -4429,11 +4616,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class FabricDataAgentToolParameters(_Model): """The fabric data agent tool parameters. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connections: The project connections attached to this tool. There can be a maximum of 1 connection resource attached to the tool. :vartype project_connections: list[~azure.ai.projects.models.ToolProjectConnection] """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connections: Optional[list["_models.ToolProjectConnection"]] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -4444,6 +4639,8 @@ class FabricDataAgentToolParameters(_Model): def __init__( self, *, + name: Optional[str] = None, + description: Optional[str] = None, project_connections: Optional[list["_models.ToolProjectConnection"]] = None, ) -> None: ... @@ -4578,6 +4775,10 @@ class FileSearchTool(Tool, discriminator="file_search"): :ivar filters: Is either a ComparisonFilter type or a CompoundFilter type. :vartype filters: ~azure.ai.projects.models.ComparisonFilter or ~azure.ai.projects.models.CompoundFilter + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str """ type: Literal[ToolType.FILE_SEARCH] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore @@ -4592,6 +4793,10 @@ class FileSearchTool(Tool, discriminator="file_search"): """Ranking options for search.""" filters: Optional["_types.Filters"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Is either a ComparisonFilter type or a CompoundFilter type.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" @overload def __init__( @@ -4601,6 +4806,8 @@ def __init__( max_num_results: Optional[int] = None, ranking_options: Optional["_models.RankingOptions"] = None, filters: Optional["_types.Filters"] = None, + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -4674,6 +4881,10 @@ class FunctionShellToolParam(Tool, discriminator="shell"): :vartype type: str or ~azure.ai.projects.models.SHELL :ivar environment: :vartype environment: ~azure.ai.projects.models.FunctionShellToolParamEnvironment + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str """ type: Literal[ToolType.SHELL] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore @@ -4681,12 +4892,18 @@ class FunctionShellToolParam(Tool, discriminator="shell"): environment: Optional["_models.FunctionShellToolParamEnvironment"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" @overload def __init__( self, *, environment: Optional["_models.FunctionShellToolParamEnvironment"] = None, + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -5023,6 +5240,10 @@ class ImageGenTool(Tool, discriminator="image_generation"): :ivar action: Whether to generate a new image or edit an existing image. Default: ``auto``. Known values are: "generate", "edit", and "auto". :vartype action: str or ~azure.ai.projects.models.ImageGenAction + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str """ type: Literal[ToolType.IMAGE_GENERATION] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore @@ -5078,6 +5299,10 @@ class ImageGenTool(Tool, discriminator="image_generation"): ) """Whether to generate a new image or edit an existing image. Default: ``auto``. Known values are: \"generate\", \"edit\", and \"auto\".""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" @overload def __init__( @@ -5096,6 +5321,8 @@ def __init__( input_image_mask: Optional["_models.ImageGenToolInputImageMask"] = None, partial_images: Optional[int] = None, action: Optional[Union[str, "_models.ImageGenAction"]] = None, + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -5510,14 +5737,25 @@ class LocalShellToolParam(Tool, discriminator="local_shell"): :ivar type: The type of the local shell tool. Always ``local_shell``. Required. LOCAL_SHELL. :vartype type: str or ~azure.ai.projects.models.LOCAL_SHELL + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str """ type: Literal[ToolType.LOCAL_SHELL] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The type of the local shell tool. Always ``local_shell``. Required. LOCAL_SHELL.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" @overload def __init__( self, + *, + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -5921,6 +6159,10 @@ class MemorySearchPreviewTool(Tool, discriminator="memory_search_preview"): :ivar type: The type of the tool. Always ``memory_search_preview``. Required. MEMORY_SEARCH_PREVIEW. :vartype type: str or ~azure.ai.projects.models.MEMORY_SEARCH_PREVIEW + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar memory_store_name: The name of the memory store to use. Required. :vartype memory_store_name: str :ivar scope: The namespace used to group and isolate memories, such as a user ID. Limits which @@ -5936,6 +6178,10 @@ class MemorySearchPreviewTool(Tool, discriminator="memory_search_preview"): type: Literal[ToolType.MEMORY_SEARCH_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The type of the tool. Always ``memory_search_preview``. Required. MEMORY_SEARCH_PREVIEW.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" memory_store_name: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """The name of the memory store to use. Required.""" scope: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -5955,6 +6201,8 @@ def __init__( *, memory_store_name: str, scope: str, + name: Optional[str] = None, + description: Optional[str] = None, search_options: Optional["_models.MemorySearchOptions"] = None, update_delay: Optional[int] = None, ) -> None: ... @@ -6403,6 +6651,10 @@ class MicrosoftFabricPreviewTool(Tool, discriminator="fabric_dataagent_preview") :ivar type: The object type, which is always 'fabric_dataagent_preview'. Required. FABRIC_DATAAGENT_PREVIEW. :vartype type: str or ~azure.ai.projects.models.FABRIC_DATAAGENT_PREVIEW + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar fabric_dataagent_preview: The fabric data agent tool parameters. Required. :vartype fabric_dataagent_preview: ~azure.ai.projects.models.FabricDataAgentToolParameters """ @@ -6410,6 +6662,10 @@ class MicrosoftFabricPreviewTool(Tool, discriminator="fabric_dataagent_preview") type: Literal[ToolType.FABRIC_DATAAGENT_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The object type, which is always 'fabric_dataagent_preview'. Required. FABRIC_DATAAGENT_PREVIEW.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" fabric_dataagent_preview: "_models.FabricDataAgentToolParameters" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -6420,6 +6676,8 @@ def __init__( self, *, fabric_dataagent_preview: "_models.FabricDataAgentToolParameters", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -7802,11 +8060,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class SharepointGroundingToolParameters(_Model): """The sharepoint grounding tool parameters. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connections: The project connections attached to this tool. There can be a maximum of 1 connection resource attached to the tool. :vartype project_connections: list[~azure.ai.projects.models.ToolProjectConnection] """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connections: Optional[list["_models.ToolProjectConnection"]] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -7817,6 +8083,8 @@ class SharepointGroundingToolParameters(_Model): def __init__( self, *, + name: Optional[str] = None, + description: Optional[str] = None, project_connections: Optional[list["_models.ToolProjectConnection"]] = None, ) -> None: ... @@ -7837,6 +8105,10 @@ class SharepointPreviewTool(Tool, discriminator="sharepoint_grounding_preview"): :ivar type: The object type, which is always 'sharepoint_grounding_preview'. Required. SHAREPOINT_GROUNDING_PREVIEW. :vartype type: str or ~azure.ai.projects.models.SHAREPOINT_GROUNDING_PREVIEW + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar sharepoint_grounding_preview: The sharepoint grounding tool parameters. Required. :vartype sharepoint_grounding_preview: ~azure.ai.projects.models.SharepointGroundingToolParameters @@ -7845,6 +8117,10 @@ class SharepointPreviewTool(Tool, discriminator="sharepoint_grounding_preview"): type: Literal[ToolType.SHAREPOINT_GROUNDING_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore """The object type, which is always 'sharepoint_grounding_preview'. Required. SHAREPOINT_GROUNDING_PREVIEW.""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" sharepoint_grounding_preview: "_models.SharepointGroundingToolParameters" = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -7855,6 +8131,8 @@ def __init__( self, *, sharepoint_grounding_preview: "_models.SharepointGroundingToolParameters", + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -8711,11 +8989,19 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class ToolProjectConnection(_Model): """A project connection resource. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connection_id: A project connection in a ToolProjectConnectionList attached to this tool. Required. :vartype project_connection_id: str """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connection_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A project connection in a ToolProjectConnectionList attached to this tool. Required.""" @@ -8724,6 +9010,75 @@ def __init__( self, *, project_connection_id: str, + name: Optional[str] = None, + description: Optional[str] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ToolsetObject(_Model): + """A toolset that stores reusable tool definitions for agents. + + :ivar object: The object type, which is always 'toolset'. Required. TOOLSET. + :vartype object: str or ~azure.ai.projects.models.TOOLSET + :ivar id: The unique identifier of the toolset. Required. + :vartype id: str + :ivar created_at: The Unix timestamp (seconds) when the toolset was created. Required. + :vartype created_at: ~datetime.datetime + :ivar updated_at: The Unix timestamp (seconds) when the toolset was last updated. Required. + :vartype updated_at: ~datetime.datetime + :ivar name: The name of the toolset. Required. + :vartype name: str + :ivar description: A human-readable description of the toolset. + :vartype description: str + :ivar metadata: Arbitrary key-value metadata to associate with the toolset. + :vartype metadata: dict[str, str] + :ivar tools: The list of tools contained in this toolset. Required. + :vartype tools: list[~azure.ai.projects.models.Tool] + """ + + object: Literal[ToolsetObjectType.TOOLSET] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The object type, which is always 'toolset'. Required. TOOLSET.""" + id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The unique identifier of the toolset. Required.""" + created_at: datetime.datetime = rest_field( + visibility=["read", "create", "update", "delete", "query"], format="unix-timestamp" + ) + """The Unix timestamp (seconds) when the toolset was created. Required.""" + updated_at: datetime.datetime = rest_field( + visibility=["read", "create", "update", "delete", "query"], format="unix-timestamp" + ) + """The Unix timestamp (seconds) when the toolset was last updated. Required.""" + name: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The name of the toolset. Required.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """A human-readable description of the toolset.""" + metadata: Optional[dict[str, str]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Arbitrary key-value metadata to associate with the toolset.""" + tools: list["_models.Tool"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The list of tools contained in this toolset. Required.""" + + @overload + def __init__( + self, + *, + object: Literal[ToolsetObjectType.TOOLSET], + id: str, # pylint: disable=redefined-builtin + created_at: datetime.datetime, + updated_at: datetime.datetime, + name: str, + tools: list["_models.Tool"], + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, ) -> None: ... @overload @@ -8829,6 +9184,10 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class WebSearchConfiguration(_Model): """A web search configuration for bing custom search. + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar project_connection_id: Project connection id for grounding with bing custom search. Required. :vartype project_connection_id: str @@ -8836,6 +9195,10 @@ class WebSearchConfiguration(_Model): :vartype instance_name: str """ + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" project_connection_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Project connection id for grounding with bing custom search. Required.""" instance_name: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -8847,6 +9210,8 @@ def __init__( *, project_connection_id: str, instance_name: str, + name: Optional[str] = None, + description: Optional[str] = None, ) -> None: ... @overload @@ -8921,6 +9286,10 @@ class WebSearchTool(Tool, discriminator="web_search"): for the search. One of ``low``, ``medium``, or ``high``. ``medium`` is the default. Is one of the following types: Literal["low"], Literal["medium"], Literal["high"] :vartype search_context_size: str or str or str + :ivar name: Optional user-defined name for this tool or configuration. + :vartype name: str + :ivar description: Optional user-defined description for this tool or configuration. + :vartype description: str :ivar custom_search_configuration: The project connections attached to this tool. There can be a maximum of 1 connection resource attached to the tool. :vartype custom_search_configuration: ~azure.ai.projects.models.WebSearchConfiguration @@ -8941,6 +9310,10 @@ class WebSearchTool(Tool, discriminator="web_search"): """High level guidance for the amount of context window space to use for the search. One of ``low``, ``medium``, or ``high``. ``medium`` is the default. Is one of the following types: Literal[\"low\"], Literal[\"medium\"], Literal[\"high\"]""" + name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined name for this tool or configuration.""" + description: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional user-defined description for this tool or configuration.""" custom_search_configuration: Optional["_models.WebSearchConfiguration"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -8954,6 +9327,8 @@ def __init__( filters: Optional["_models.WebSearchToolFilters"] = None, user_location: Optional["_models.WebSearchApproximateLocation"] = None, search_context_size: Optional[Literal["low", "medium", "high"]] = None, + name: Optional[str] = None, + description: Optional[str] = None, custom_search_configuration: Optional["_models.WebSearchConfiguration"] = None, ) -> None: ... @@ -9065,3 +9440,66 @@ def __init__(self, mapping: Mapping[str, Any]) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.kind = AgentKind.WORKFLOW # type: ignore + + +class WorkIQPreviewTool(Tool, discriminator="work_iq_preview"): + """A WorkIQ server-side tool. + + :ivar type: The object type, which is always 'work_iq_preview'. Required. WORK_IQ_PREVIEW. + :vartype type: str or ~azure.ai.projects.models.WORK_IQ_PREVIEW + :ivar work_iq_preview: The WorkIQ tool parameters. Required. + :vartype work_iq_preview: ~azure.ai.projects.models.WorkIQPreviewToolParameters + """ + + type: Literal[ToolType.WORK_IQ_PREVIEW] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """The object type, which is always 'work_iq_preview'. Required. WORK_IQ_PREVIEW.""" + work_iq_preview: "_models.WorkIQPreviewToolParameters" = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + """The WorkIQ tool parameters. Required.""" + + @overload + def __init__( + self, + *, + work_iq_preview: "_models.WorkIQPreviewToolParameters", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.type = ToolType.WORK_IQ_PREVIEW # type: ignore + + +class WorkIQPreviewToolParameters(_Model): + """The WorkIQ tool parameters. + + :ivar project_connection_id: The ID of the WorkIQ project connection. Required. + :vartype project_connection_id: str + """ + + project_connection_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The ID of the WorkIQ project connection. Required.""" + + @overload + def __init__( + self, + *, + project_connection_id: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index 21502f0de2bb..5a9b2fc628c9 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -80,114 +80,6 @@ def build_agents_get_request(agent_name: str, **kwargs: Any) -> HttpRequest: return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_agents_create_agent_request( - *, foundry_features: Optional[Union[str, _AgentDefinitionOptInKeys]] = None, **kwargs: Any -) -> HttpRequest: - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) - accept = _headers.pop("Accept", "application/json") - - # Construct URL - _url = "/agents" - - # Construct parameters - _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - - # Construct headers - if foundry_features is not None: - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") - if content_type is not None: - _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") - _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") - - return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) - - -def build_agents_update_agent_request( - agent_name: str, *, foundry_features: Optional[Union[str, _AgentDefinitionOptInKeys]] = None, **kwargs: Any -) -> HttpRequest: - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) - accept = _headers.pop("Accept", "application/json") - - # Construct URL - _url = "/agents/{agent_name}" - path_format_arguments = { - "agent_name": _SERIALIZER.url("agent_name", agent_name, "str"), - } - - _url: str = _url.format(**path_format_arguments) # type: ignore - - # Construct parameters - _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - - # Construct headers - if foundry_features is not None: - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") - if content_type is not None: - _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") - _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") - - return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) - - -def build_agents_create_agent_from_manifest_request(**kwargs: Any) -> HttpRequest: # pylint: disable=name-too-long - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) - accept = _headers.pop("Accept", "application/json") - - # Construct URL - _url = "/agents:import" - - # Construct parameters - _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - - # Construct headers - if content_type is not None: - _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") - _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") - - return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) - - -def build_agents_update_agent_from_manifest_request( # pylint: disable=name-too-long - agent_name: str, **kwargs: Any -) -> HttpRequest: - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) - accept = _headers.pop("Accept", "application/json") - - # Construct URL - _url = "/agents/{agent_name}/import" - path_format_arguments = { - "agent_name": _SERIALIZER.url("agent_name", agent_name, "str"), - } - - _url: str = _url.format(**path_format_arguments) # type: ignore - - # Construct parameters - _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - - # Construct headers - if content_type is not None: - _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") - _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") - - return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) - - def build_agents_delete_request(agent_name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1916,7 +1808,152 @@ def build_beta_schedules_list_runs_request( return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -class BetaOperations: +def build_beta_toolsets_create_request( + *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/toolsets" + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_beta_toolsets_update_request( + tool_set_name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/toolsets/{tool_set_name}" + path_format_arguments = { + "tool_set_name": _SERIALIZER.url("tool_set_name", tool_set_name, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_beta_toolsets_get_request( + tool_set_name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/toolsets/{tool_set_name}" + path_format_arguments = { + "tool_set_name": _SERIALIZER.url("tool_set_name", tool_set_name, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_beta_toolsets_list_request( + *, + foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + after: Optional[str] = None, + before: Optional[str] = None, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/toolsets" + + # Construct parameters + if limit is not None: + _params["limit"] = _SERIALIZER.query("limit", limit, "int") + if order is not None: + _params["order"] = _SERIALIZER.query("order", order, "str") + if after is not None: + _params["after"] = _SERIALIZER.query("after", after, "str") + if before is not None: + _params["before"] = _SERIALIZER.query("before", before, "str") + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_beta_toolsets_delete_request( + tool_set_name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/toolsets/{tool_set_name}" + path_format_arguments = { + "tool_set_name": _SERIALIZER.url("tool_set_name", tool_set_name, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) + + +class BetaOperations: # pylint: disable=too-many-instance-attributes """ .. warning:: **DO NOT** instantiate this class directly. @@ -1941,6 +1978,7 @@ def __init__(self, *args, **kwargs) -> None: self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) self.red_teams = BetaRedTeamsOperations(self._client, self._config, self._serialize, self._deserialize) self.schedules = BetaSchedulesOperations(self._client, self._config, self._serialize, self._deserialize) + self.toolsets = BetaToolsetsOperations(self._client, self._config, self._serialize, self._deserialize) class AgentsOperations: @@ -2025,65 +2063,16 @@ def get(self, agent_name: str, **kwargs: Any) -> _models.AgentDetails: return deserialized # type: ignore - @overload - def _create_agent( - self, - *, - name: str, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - def _create_agent( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - def _create_agent( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @distributed_trace - def _create_agent( - self, - body: Union[JSON, IO[bytes]] = _Unset, - *, - name: str = _Unset, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: - """Creates the agent. - - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: + """Deletes an agent. - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :paramtype name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :param agent_name: The name of the agent to delete. Required. + :type agent_name: str + :return: DeleteAgentResponse. The DeleteAgentResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DeleteAgentResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -2092,31 +2081,14 @@ def _create_agent( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) - - if body is _Unset: - if name is _Unset: - raise TypeError("missing required argument: name") - if definition is _Unset: - raise TypeError("missing required argument: definition") - body = {"definition": definition, "description": description, "metadata": metadata, "name": name} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.DeleteAgentResponse] = kwargs.pop("cls", None) - _request = build_agents_create_agent_request( - foundry_features=_foundry_features, - content_type=content_type, + _request = build_agents_delete_request( + agent_name=agent_name, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -2149,15 +2121,110 @@ def _create_agent( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.DeleteAgentResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + @distributed_trace + def list( + self, + *, + kind: Optional[Union[str, _models.AgentKind]] = None, + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + before: Optional[str] = None, + **kwargs: Any + ) -> ItemPaged["_models.AgentDetails"]: + """Returns the list of all agents. + + :keyword kind: Filter agents by kind. If not provided, all agents are returned. Known values + are: "prompt", "hosted", and "workflow". Default value is None. + :paramtype kind: str or ~azure.ai.projects.models.AgentKind + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the + default is 20. Default value is None. + :paramtype limit: int + :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for + ascending order and``desc`` + for descending order. Known values are: "asc" and "desc". Default value is None. + :paramtype order: str or ~azure.ai.projects.models.PageOrder + :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your + place in the list. + For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page of the list. + Default value is None. + :paramtype before: str + :return: An iterator like instance of AgentDetails + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.AgentDetails] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.AgentDetails]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(_continuation_token=None): + + _request = build_agents_list_request( + kind=kind, + limit=limit, + order=order, + after=_continuation_token, + before=before, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.AgentDetails], + deserialized.get("data", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("last_id") or None, iter(list_of_elem) + + def get_next(_continuation_token=None): + _request = prepare_request(_continuation_token) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + @overload - def _update_agent( + def create_version( self, agent_name: str, *, @@ -2166,18 +2233,84 @@ def _update_agent( metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + @overload - def _update_agent( + def create_version( self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + @overload - def _update_agent( + def create_version( self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace - def _update_agent( + def create_version( self, agent_name: str, body: Union[JSON, IO[bytes]] = _Unset, @@ -2186,11 +2319,15 @@ def _update_agent( metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: - """Updates the agent by adding a new version if there are any changes to the agent definition. If - no changes, returns the existing agent version. + ) -> _models.AgentVersionDetails: + """Create a new agent version. - :param agent_name: The name of the agent to retrieve. Required. + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. :type agent_name: str :param body: Is either a JSON type or a IO[bytes] type. Required. :type body: JSON or IO[bytes] @@ -2206,8 +2343,8 @@ def _update_agent( :paramtype metadata: dict[str, str] :keyword description: A human-readable description of the agent. Default value is None. :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore @@ -2223,7 +2360,7 @@ def _update_agent( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) if body is _Unset: if definition is _Unset: @@ -2237,7 +2374,7 @@ def _update_agent( else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_agents_update_agent_request( + _request = build_agents_create_version_request( agent_name=agent_name, foundry_features=_foundry_features, content_type=content_type, @@ -2275,7 +2412,7 @@ def _update_agent( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.AgentVersionDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -2283,49 +2420,117 @@ def _update_agent( return deserialized # type: ignore @overload - def _create_agent_from_manifest( + def create_version_from_manifest( self, + agent_name: str, *, - name: str, manifest_id: str, parameter_values: dict[str, Any], content_type: str = "application/json", metadata: Optional[dict[str, str]] = None, description: Optional[str] = None, **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - def _create_agent_from_manifest( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - def _create_agent_from_manifest( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. - @distributed_trace - def _create_agent_from_manifest( - self, - body: Union[JSON, IO[bytes]] = _Unset, - *, - name: str = _Unset, - manifest_id: str = _Unset, - parameter_values: dict[str, Any] = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: - """Creates an agent from a manifest. + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword name: The unique name that identifies the agent. Name can be used to + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword manifest_id: The manifest ID to import the agent version from. Required. + :paramtype manifest_id: str + :keyword parameter_values: The inputs to the manifest that will result in a fully materialized + Agent. Required. + :paramtype parameter_values: dict[str, any] + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def create_version_from_manifest( + self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. + + :param agent_name: The unique name that identifies the agent. Name can be used to retrieve/update/delete the agent. * Must start and end with alphanumeric characters, * Can contain hyphens in the middle * Must not exceed 63 characters. Required. - :paramtype name: str + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def create_version_from_manifest( + self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def create_version_from_manifest( + self, + agent_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + manifest_id: str = _Unset, + parameter_values: dict[str, Any] = _Unset, + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version from a manifest. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] :keyword manifest_id: The manifest ID to import the agent version from. Required. :paramtype manifest_id: str :keyword parameter_values: The inputs to the manifest that will result in a fully materialized @@ -2340,8 +2545,8 @@ def _create_agent_from_manifest( :paramtype metadata: dict[str, str] :keyword description: A human-readable description of the agent. Default value is None. :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2356,11 +2561,9 @@ def _create_agent_from_manifest( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) if body is _Unset: - if name is _Unset: - raise TypeError("missing required argument: name") if manifest_id is _Unset: raise TypeError("missing required argument: manifest_id") if parameter_values is _Unset: @@ -2369,7 +2572,6 @@ def _create_agent_from_manifest( "description": description, "manifest_id": manifest_id, "metadata": metadata, - "name": name, "parameter_values": parameter_values, } body = {k: v for k, v in body.items() if v is not None} @@ -2380,7 +2582,8 @@ def _create_agent_from_manifest( else: _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_agents_create_agent_from_manifest_request( + _request = build_agents_create_version_from_manifest_request( + agent_name=agent_name, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -2416,69 +2619,23 @@ def _create_agent_from_manifest( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.AgentVersionDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - def _update_agent_from_manifest( - self, - agent_name: str, - *, - manifest_id: str, - parameter_values: dict[str, Any], - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - def _update_agent_from_manifest( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @overload - def _update_agent_from_manifest( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentDetails: ... - @distributed_trace - def _update_agent_from_manifest( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - manifest_id: str = _Unset, - parameter_values: dict[str, Any] = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentDetails: - """Updates the agent from a manifest by adding a new version if there are any changes to the agent - definition. If no changes, returns the existing agent version. + def get_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _models.AgentVersionDetails: + """Retrieves a specific version of an agent. - :param agent_name: The name of the agent to update. Required. + :param agent_name: The name of the agent to retrieve. Required. :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword manifest_id: The manifest ID to import the agent version from. Required. - :paramtype manifest_id: str - :keyword parameter_values: The inputs to the manifest that will result in a fully materialized - Agent. Required. - :paramtype parameter_values: dict[str, any] - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentDetails. The AgentDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentDetails + :param agent_version: The version of the agent to retrieve. Required. + :type agent_version: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2489,36 +2646,15 @@ def _update_agent_from_manifest( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentDetails] = kwargs.pop("cls", None) - - if body is _Unset: - if manifest_id is _Unset: - raise TypeError("missing required argument: manifest_id") - if parameter_values is _Unset: - raise TypeError("missing required argument: parameter_values") - body = { - "description": description, - "manifest_id": manifest_id, - "metadata": metadata, - "parameter_values": parameter_values, - } - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) - _request = build_agents_update_agent_from_manifest_request( + _request = build_agents_get_version_request( agent_name=agent_name, - content_type=content_type, + agent_version=agent_version, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -2551,7 +2687,7 @@ def _update_agent_from_manifest( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentDetails, response.json()) + deserialized = _deserialize(_models.AgentVersionDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -2559,13 +2695,16 @@ def _update_agent_from_manifest( return deserialized # type: ignore @distributed_trace - def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: - """Deletes an agent. + def delete_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _models.DeleteAgentVersionResponse: + """Deletes a specific version of an agent. :param agent_name: The name of the agent to delete. Required. :type agent_name: str - :return: DeleteAgentResponse. The DeleteAgentResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DeleteAgentResponse + :param agent_version: The version of the agent to delete. Required. + :type agent_version: str + :return: DeleteAgentVersionResponse. The DeleteAgentVersionResponse is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.DeleteAgentVersionResponse :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -2579,10 +2718,11 @@ def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.DeleteAgentResponse] = kwargs.pop("cls", None) + cls: ClsType[_models.DeleteAgentVersionResponse] = kwargs.pop("cls", None) - _request = build_agents_delete_request( + _request = build_agents_delete_version_request( agent_name=agent_name, + agent_version=agent_version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -2616,7 +2756,7 @@ def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DeleteAgentResponse, response.json()) + deserialized = _deserialize(_models.DeleteAgentVersionResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -2624,20 +2764,19 @@ def delete(self, agent_name: str, **kwargs: Any) -> _models.DeleteAgentResponse: return deserialized # type: ignore @distributed_trace - def list( + def list_versions( self, + agent_name: str, *, - kind: Optional[Union[str, _models.AgentKind]] = None, limit: Optional[int] = None, order: Optional[Union[str, _models.PageOrder]] = None, before: Optional[str] = None, **kwargs: Any - ) -> ItemPaged["_models.AgentDetails"]: - """Returns the list of all agents. + ) -> ItemPaged["_models.AgentVersionDetails"]: + """Returns the list of versions of an agent. - :keyword kind: Filter agents by kind. If not provided, all agents are returned. Known values - are: "prompt", "hosted", and "workflow". Default value is None. - :paramtype kind: str or ~azure.ai.projects.models.AgentKind + :param agent_name: The name of the agent to retrieve versions for. Required. + :type agent_name: str :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and 100, and the default is 20. Default value is None. @@ -2652,14 +2791,14 @@ def list( subsequent call can include before=obj_foo in order to fetch the previous page of the list. Default value is None. :paramtype before: str - :return: An iterator like instance of AgentDetails - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.AgentDetails] + :return: An iterator like instance of AgentVersionDetails + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.AgentVersionDetails] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.AgentDetails]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.AgentVersionDetails]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -2671,8 +2810,8 @@ def list( def prepare_request(_continuation_token=None): - _request = build_agents_list_request( - kind=kind, + _request = build_agents_list_versions_request( + agent_name=agent_name, limit=limit, order=order, after=_continuation_token, @@ -2690,7 +2829,7 @@ def prepare_request(_continuation_token=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.AgentDetails], + List[_models.AgentVersionDetails], deserialized.get("data", []), ) if cls: @@ -2718,131 +2857,34 @@ def get_next(_continuation_token=None): return ItemPaged(get_next, extract_data) - @overload - def create_version( - self, - agent_name: str, - *, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. +class EvaluationRulesOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def create_version( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def create_version( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`evaluation_rules` attribute. + """ - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def create_version( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: + """Get an evaluation rule. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -2851,30 +2893,14 @@ def create_version( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) - - if body is _Unset: - if definition is _Unset: - raise TypeError("missing required argument: definition") - body = {"definition": definition, "description": description, "metadata": metadata} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) - _request = build_agents_create_version_request( - agent_name=agent_name, - foundry_features=_foundry_features, - content_type=content_type, + _request = build_evaluation_rules_get_request( + id=id, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -2898,152 +2924,136 @@ def create_version( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentVersionDetails, response.json()) + deserialized = _deserialize(_models.EvaluationRule, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - def create_version_from_manifest( - self, - agent_name: str, - *, - manifest_id: str, - parameter_values: dict[str, Any], - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword manifest_id: The manifest ID to import the agent version from. Required. - :paramtype manifest_id: str - :keyword parameter_values: The inputs to the manifest that will result in a fully materialized - Agent. Required. - :paramtype parameter_values: dict[str, any] - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + @distributed_trace + def delete(self, id: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements + """Delete an evaluation rule. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :return: None + :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - @overload - def create_version_from_manifest( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + cls: ClsType[None] = kwargs.pop("cls", None) - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON + _request = build_evaluation_rules_delete_request( + id=id, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [204]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if cls: + return cls(pipeline_response, None, {}) # type: ignore + + @overload + def create_or_update( + self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_version_from_manifest( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. + def create_or_update( + self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] + @overload + def create_or_update( + self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create_version_from_manifest( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - manifest_id: str = _Unset, - parameter_values: dict[str, Any] = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version from a manifest. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword manifest_id: The manifest ID to import the agent version from. Required. - :paramtype manifest_id: str - :keyword parameter_values: The inputs to the manifest that will result in a fully materialized - Agent. Required. - :paramtype parameter_values: dict[str, any] - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. + def create_or_update( + self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Is one of the following types: + EvaluationRule, JSON, IO[bytes] Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3056,29 +3066,18 @@ def create_version_from_manifest( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) - if body is _Unset: - if manifest_id is _Unset: - raise TypeError("missing required argument: manifest_id") - if parameter_values is _Unset: - raise TypeError("missing required argument: parameter_values") - body = { - "description": description, - "manifest_id": manifest_id, - "metadata": metadata, - "parameter_values": parameter_values, - } - body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(evaluation_rule, (IOBase, bytes)): + _content = evaluation_rule else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(evaluation_rule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_agents_create_version_from_manifest_request( - agent_name=agent_name, + _request = build_evaluation_rules_create_or_update_request( + id=id, + foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3098,23 +3097,19 @@ def create_version_from_manifest( response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [200, 201]: if _stream: try: response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.AgentVersionDetails, response.json()) + deserialized = _deserialize(_models.EvaluationRule, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -3122,17 +3117,32 @@ def create_version_from_manifest( return deserialized # type: ignore @distributed_trace - def get_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _models.AgentVersionDetails: - """Retrieves a specific version of an agent. + def list( + self, + *, + action_type: Optional[Union[str, _models.EvaluationRuleActionType]] = None, + agent_name: Optional[str] = None, + enabled: Optional[bool] = None, + **kwargs: Any + ) -> ItemPaged["_models.EvaluationRule"]: + """List all evaluation rules. - :param agent_name: The name of the agent to retrieve. Required. - :type agent_name: str - :param agent_version: The version of the agent to retrieve. Required. - :type agent_version: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails + :keyword action_type: Filter by the type of evaluation rule. Known values are: + "continuousEvaluation" and "humanEvaluationPreview". Default value is None. + :paramtype action_type: str or ~azure.ai.projects.models.EvaluationRuleActionType + :keyword agent_name: Filter by the agent name. Default value is None. + :paramtype agent_name: str + :keyword enabled: Filter by the enabled status. Default value is None. + :paramtype enabled: bool + :return: An iterator like instance of EvaluationRule + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluationRule] :raises ~azure.core.exceptions.HttpResponseError: """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.EvaluationRule]] = kwargs.pop("cls", None) + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3141,68 +3151,102 @@ def get_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _mo } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} + def prepare_request(next_link=None): + if not next_link: - cls: ClsType[_models.AgentVersionDetails] = kwargs.pop("cls", None) + _request = build_evaluation_rules_list_request( + action_type=action_type, + agent_name=agent_name, + enabled=enabled, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _request = build_agents_get_version_request( - agent_name=agent_name, - agent_version=agent_version, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) + return _request - response = pipeline_response.http_response + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.EvaluationRule], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, iter(list_of_elem) - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs ) - raise HttpResponseError(response=response, model=error) + response = pipeline_response.http_response - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.AgentVersionDetails, response.json()) + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return pipeline_response - return deserialized # type: ignore + return ItemPaged(get_next, extract_data) - @distributed_trace - def delete_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _models.DeleteAgentVersionResponse: - """Deletes a specific version of an agent. - :param agent_name: The name of the agent to delete. Required. - :type agent_name: str - :param agent_version: The version of the agent to delete. Required. - :type agent_version: str - :return: DeleteAgentVersionResponse. The DeleteAgentVersionResponse is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.DeleteAgentVersionResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - error_map: MutableMapping = { +class ConnectionsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`connections` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @distributed_trace + def _get(self, name: str, **kwargs: Any) -> _models.Connection: + """Get a connection by name, without populating connection credentials. + + :param name: The friendly name of the connection, provided by the user. Required. + :type name: str + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError, @@ -3213,11 +3257,10 @@ def delete_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.DeleteAgentVersionResponse] = kwargs.pop("cls", None) + cls: ClsType[_models.Connection] = kwargs.pop("cls", None) - _request = build_agents_delete_version_request( - agent_name=agent_name, - agent_version=agent_version, + _request = build_connections_get_request( + name=name, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3242,58 +3285,114 @@ def delete_version(self, agent_name: str, agent_version: str, **kwargs: Any) -> except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) + + response_headers = {} + response_headers["x-ms-client-request-id"] = self._deserialize( + "str", response.headers.get("x-ms-client-request-id") + ) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DeleteAgentVersionResponse, response.json()) + deserialized = _deserialize(_models.Connection, response.json()) if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return cls(pipeline_response, deserialized, response_headers) # type: ignore return deserialized # type: ignore @distributed_trace - def list_versions( + def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: + """Get a connection by name, with its connection credentials. + + :param name: The friendly name of the connection, provided by the user. Required. + :type name: str + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.Connection] = kwargs.pop("cls", None) + + _request = build_connections_get_with_credentials_request( + name=name, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + response_headers = {} + response_headers["x-ms-client-request-id"] = self._deserialize( + "str", response.headers.get("x-ms-client-request-id") + ) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Connection, response.json()) + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + def list( self, - agent_name: str, *, - limit: Optional[int] = None, - order: Optional[Union[str, _models.PageOrder]] = None, - before: Optional[str] = None, + connection_type: Optional[Union[str, _models.ConnectionType]] = None, + default_connection: Optional[bool] = None, **kwargs: Any - ) -> ItemPaged["_models.AgentVersionDetails"]: - """Returns the list of versions of an agent. + ) -> ItemPaged["_models.Connection"]: + """List all connections in the project, without populating connection credentials. - :param agent_name: The name of the agent to retrieve versions for. Required. - :type agent_name: str - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the - default is 20. Default value is None. - :paramtype limit: int - :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for - ascending order and``desc`` - for descending order. Known values are: "asc" and "desc". Default value is None. - :paramtype order: str or ~azure.ai.projects.models.PageOrder - :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your - place in the list. - For instance, if you make a list request and receive 100 objects, ending with obj_foo, your - subsequent call can include before=obj_foo in order to fetch the previous page of the list. - Default value is None. - :paramtype before: str - :return: An iterator like instance of AgentVersionDetails - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.AgentVersionDetails] + :keyword connection_type: List connections of this specific type. Known values are: + "AzureOpenAI", "AzureBlob", "AzureStorageAccount", "CognitiveSearch", "CosmosDB", "ApiKey", + "AppConfig", "AppInsights", "CustomKeys", and "RemoteTool_Preview". Default value is None. + :paramtype connection_type: str or ~azure.ai.projects.models.ConnectionType + :keyword default_connection: List connections that are default connections. Default value is + None. + :paramtype default_connection: bool + :return: An iterator like instance of Connection + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Connection] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.AgentVersionDetails]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Connection]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -3303,36 +3402,57 @@ def list_versions( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(_continuation_token=None): + def prepare_request(next_link=None): + if not next_link: + + _request = build_connections_list_request( + connection_type=connection_type, + default_connection=default_connection, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _request = build_agents_list_versions_request( - agent_name=agent_name, - limit=limit, - order=order, - after=_continuation_token, - before=before, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) return _request def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.AgentVersionDetails], - deserialized.get("data", []), + List[_models.Connection], + deserialized.get("value", []), ) if cls: list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("last_id") or None, iter(list_of_elem) + return deserialized.get("nextLink") or None, iter(list_of_elem) - def get_next(_continuation_token=None): - _request = prepare_request(_continuation_token) + def get_next(next_link=None): + _request = prepare_request(next_link) _stream = False pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access @@ -3342,25 +3462,21 @@ def get_next(_continuation_token=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) + raise HttpResponseError(response=response) return pipeline_response return ItemPaged(get_next, extract_data) -class EvaluationRulesOperations: +class DatasetsOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`evaluation_rules` attribute. + :attr:`datasets` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -3371,15 +3487,20 @@ def __init__(self, *args, **kwargs) -> None: self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: - """Get an evaluation rule. + def list_versions(self, name: str, **kwargs: Any) -> ItemPaged["_models.DatasetVersion"]: + """List all versions of the given DatasetVersion. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :param name: The name of the resource. Required. + :type name: str + :return: An iterator like instance of DatasetVersion + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.DatasetVersion] :raises ~azure.core.exceptions.HttpResponseError: """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3388,16 +3509,188 @@ def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) + def prepare_request(next_link=None): + if not next_link: - _request = build_evaluation_rules_get_request( - id=id, - api_version=self._config.api_version, - headers=_headers, - params=_params, + _request = build_datasets_list_versions_request( + name=name, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.DatasetVersion], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, iter(list_of_elem) + + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + + @distributed_trace + def list(self, **kwargs: Any) -> ItemPaged["_models.DatasetVersion"]: + """List the latest version of each DatasetVersion. + + :return: An iterator like instance of DatasetVersion + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.DatasetVersion] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(next_link=None): + if not next_link: + + _request = build_datasets_list_request( + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.DatasetVersion], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, iter(list_of_elem) + + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + + @distributed_trace + def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: + """Get the specific version of the DatasetVersion. The service returns 404 Not Found error if the + DatasetVersion does not exist. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to retrieve. Required. + :type version: str + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) + + _request = build_datasets_get_request( + name=name, + version=version, + api_version=self._config.api_version, + headers=_headers, + params=_params, ) path_format_arguments = { "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), @@ -3424,7 +3717,7 @@ def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationRule, response.json()) + deserialized = _deserialize(_models.DatasetVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -3432,11 +3725,14 @@ def get(self, id: str, **kwargs: Any) -> _models.EvaluationRule: return deserialized # type: ignore @distributed_trace - def delete(self, id: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements - """Delete an evaluation rule. + def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements + """Delete the specific version of the DatasetVersion. The service returns 204 No Content if the + DatasetVersion was deleted successfully or if the DatasetVersion does not exist. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the DatasetVersion to delete. Required. + :type version: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: @@ -3454,8 +3750,9 @@ def delete(self, id: str, **kwargs: Any) -> None: # pylint: disable=inconsisten cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_evaluation_rules_delete_request( - id=id, + _request = build_datasets_delete_request( + name=name, + version=version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3481,74 +3778,99 @@ def delete(self, id: str, **kwargs: Any) -> None: # pylint: disable=inconsisten @overload def create_or_update( - self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, + name: str, + version: str, + dataset_version: _models.DatasetVersion, + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Required. + :type dataset_version: ~azure.ai.projects.models.DatasetVersion :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload def create_or_update( - self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, + name: str, + version: str, + dataset_version: JSON, + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: JSON + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Required. + :type dataset_version: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload def create_or_update( - self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, + name: str, + version: str, + dataset_version: IO[bytes], + *, + content_type: str = "application/merge-patch+json", + **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: IO[bytes] + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Required. + :type dataset_version: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". + Default value is "application/merge-patch+json". :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace def create_or_update( - self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. + self, name: str, version: str, dataset_version: Union[_models.DatasetVersion, JSON, IO[bytes]], **kwargs: Any + ) -> _models.DatasetVersion: + """Create a new or update an existing DatasetVersion with the given version id. - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Is one of the following types: - EvaluationRule, JSON, IO[bytes] Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to create or update. Required. + :type version: str + :param dataset_version: The DatasetVersion to create or update. Is one of the following types: + DatasetVersion, JSON, IO[bytes] Required. + :type dataset_version: ~azure.ai.projects.models.DatasetVersion or JSON or IO[bytes] + :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3561,18 +3883,18 @@ def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluationRule] = kwargs.pop("cls", None) + cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) - content_type = content_type or "application/json" + content_type = content_type or "application/merge-patch+json" _content = None - if isinstance(evaluation_rule, (IOBase, bytes)): - _content = evaluation_rule + if isinstance(dataset_version, (IOBase, bytes)): + _content = dataset_version else: - _content = json.dumps(evaluation_rule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(dataset_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_evaluation_rules_create_or_update_request( - id=id, - foundry_features=_foundry_features, + _request = build_datasets_create_or_update_request( + name=name, + version=version, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3604,40 +3926,113 @@ def create_or_update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationRule, response.json()) + deserialized = _deserialize(_models.DatasetVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace - def list( + @overload + def pending_upload( self, + name: str, + version: str, + pending_upload_request: _models.PendingUploadRequest, *, - action_type: Optional[Union[str, _models.EvaluationRuleActionType]] = None, - agent_name: Optional[str] = None, - enabled: Optional[bool] = None, + content_type: str = "application/json", **kwargs: Any - ) -> ItemPaged["_models.EvaluationRule"]: - """List all evaluation rules. + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. - :keyword action_type: Filter by the type of evaluation rule. Known values are: - "continuousEvaluation" and "humanEvaluationPreview". Default value is None. - :paramtype action_type: str or ~azure.ai.projects.models.EvaluationRuleActionType - :keyword agent_name: Filter by the agent name. Default value is None. - :paramtype agent_name: str - :keyword enabled: Filter by the enabled status. Default value is None. - :paramtype enabled: bool - :return: An iterator like instance of EvaluationRule - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluationRule] + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.EvaluationRule]] = kwargs.pop("cls", None) + @overload + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. + + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of a dataset for a specific version. + :param name: The name of the resource. Required. + :type name: str + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Is one of the following + types: PendingUploadRequest, JSON, IO[bytes] Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or + IO[bytes] + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3646,99 +4041,70 @@ def list( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(next_link=None): - if not next_link: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - _request = build_evaluation_rules_list_request( - action_type=action_type, - agent_name=agent_name, - enabled=enabled, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type = content_type or "application/json" + _content = None + if isinstance(pending_upload_request, (IOBase, bytes)): + _content = pending_upload_request + else: + _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - return _request + _request = build_datasets_pending_upload_request( + name=name, + version=version, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.EvaluationRule], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, iter(list_of_elem) + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) - def get_next(next_link=None): - _request = prepare_request(next_link) - - _stream = False - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - return pipeline_response - - return ItemPaged(get_next, extract_data) + response = pipeline_response.http_response + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) -class ConnectionsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.PendingUploadResponse, response.json()) - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`connections` attribute. - """ + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + return deserialized # type: ignore @distributed_trace - def _get(self, name: str, **kwargs: Any) -> _models.Connection: - """Get a connection by name, without populating connection credentials. + def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with a Dataset version. - :param name: The friendly name of the connection, provided by the user. Required. + :param name: The name of the resource. Required. :type name: str - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection + :param version: The specific version id of the DatasetVersion to operate on. Required. + :type version: str + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -3752,10 +4118,11 @@ def _get(self, name: str, **kwargs: Any) -> _models.Connection: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Connection] = kwargs.pop("cls", None) + cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) - _request = build_connections_get_request( + _request = build_datasets_get_credentials_request( name=name, + version=version, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3782,29 +4149,42 @@ def _get(self, name: str, **kwargs: Any) -> _models.Connection: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) - response_headers = {} - response_headers["x-ms-client-request-id"] = self._deserialize( - "str", response.headers.get("x-ms-client-request-id") - ) - if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Connection, response.json()) + deserialized = _deserialize(_models.DatasetCredential, response.json()) if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + +class DeploymentsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`deployments` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @distributed_trace - def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: - """Get a connection by name, with its connection credentials. + def get(self, name: str, **kwargs: Any) -> _models.Deployment: + """Get a deployed model. - :param name: The friendly name of the connection, provided by the user. Required. + :param name: Name of the deployment. Required. :type name: str - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection + :return: Deployment. The Deployment is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Deployment :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -3818,9 +4198,9 @@ def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Connection] = kwargs.pop("cls", None) + cls: ClsType[_models.Deployment] = kwargs.pop("cls", None) - _request = build_connections_get_with_credentials_request( + _request = build_deployments_get_request( name=name, api_version=self._config.api_version, headers=_headers, @@ -3856,7 +4236,7 @@ def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Connection, response.json()) + deserialized = _deserialize(_models.Deployment, response.json()) if cls: return cls(pipeline_response, deserialized, response_headers) # type: ignore @@ -3867,27 +4247,29 @@ def _get_with_credentials(self, name: str, **kwargs: Any) -> _models.Connection: def list( self, *, - connection_type: Optional[Union[str, _models.ConnectionType]] = None, - default_connection: Optional[bool] = None, + model_publisher: Optional[str] = None, + model_name: Optional[str] = None, + deployment_type: Optional[Union[str, _models.DeploymentType]] = None, **kwargs: Any - ) -> ItemPaged["_models.Connection"]: - """List all connections in the project, without populating connection credentials. + ) -> ItemPaged["_models.Deployment"]: + """List all deployed models in the project. - :keyword connection_type: List connections of this specific type. Known values are: - "AzureOpenAI", "AzureBlob", "AzureStorageAccount", "CognitiveSearch", "CosmosDB", "ApiKey", - "AppConfig", "AppInsights", "CustomKeys", and "RemoteTool_Preview". Default value is None. - :paramtype connection_type: str or ~azure.ai.projects.models.ConnectionType - :keyword default_connection: List connections that are default connections. Default value is - None. - :paramtype default_connection: bool - :return: An iterator like instance of Connection - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Connection] + :keyword model_publisher: Model publisher to filter models by. Default value is None. + :paramtype model_publisher: str + :keyword model_name: Model name (the publisher specific name) to filter models by. Default + value is None. + :paramtype model_name: str + :keyword deployment_type: Type of deployment to filter list by. "ModelDeployment" Default value + is None. + :paramtype deployment_type: str or ~azure.ai.projects.models.DeploymentType + :return: An iterator like instance of Deployment + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Deployment] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Connection]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Deployment]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -3900,9 +4282,10 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_connections_list_request( - connection_type=connection_type, - default_connection=default_connection, + _request = build_deployments_list_request( + model_publisher=model_publisher, + model_name=model_name, + deployment_type=deployment_type, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3939,7 +4322,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Connection], + List[_models.Deployment], deserialized.get("value", []), ) if cls: @@ -3964,14 +4347,14 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) -class DatasetsOperations: +class IndexesOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`datasets` attribute. + :attr:`indexes` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -3982,19 +4365,19 @@ def __init__(self, *args, **kwargs) -> None: self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def list_versions(self, name: str, **kwargs: Any) -> ItemPaged["_models.DatasetVersion"]: - """List all versions of the given DatasetVersion. + def list_versions(self, name: str, **kwargs: Any) -> ItemPaged["_models.Index"]: + """List all versions of the given Index. :param name: The name of the resource. Required. :type name: str - :return: An iterator like instance of DatasetVersion - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.DatasetVersion] + :return: An iterator like instance of Index + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Index] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4007,7 +4390,7 @@ def list_versions(self, name: str, **kwargs: Any) -> ItemPaged["_models.DatasetV def prepare_request(next_link=None): if not next_link: - _request = build_datasets_list_versions_request( + _request = build_indexes_list_versions_request( name=name, api_version=self._config.api_version, headers=_headers, @@ -4045,7 +4428,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.DatasetVersion], + List[_models.Index], deserialized.get("value", []), ) if cls: @@ -4070,17 +4453,17 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) @distributed_trace - def list(self, **kwargs: Any) -> ItemPaged["_models.DatasetVersion"]: - """List the latest version of each DatasetVersion. + def list(self, **kwargs: Any) -> ItemPaged["_models.Index"]: + """List the latest version of each Index. - :return: An iterator like instance of DatasetVersion - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.DatasetVersion] + :return: An iterator like instance of Index + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Index] :raises ~azure.core.exceptions.HttpResponseError: """ _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.DatasetVersion]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4093,7 +4476,7 @@ def list(self, **kwargs: Any) -> ItemPaged["_models.DatasetVersion"]: def prepare_request(next_link=None): if not next_link: - _request = build_datasets_list_request( + _request = build_indexes_list_request( api_version=self._config.api_version, headers=_headers, params=_params, @@ -4130,7 +4513,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.DatasetVersion], + List[_models.Index], deserialized.get("value", []), ) if cls: @@ -4155,16 +4538,16 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) @distributed_trace - def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: - """Get the specific version of the DatasetVersion. The service returns 404 Not Found error if the - DatasetVersion does not exist. + def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: + """Get the specific version of the Index. The service returns 404 Not Found error if the Index + does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to retrieve. Required. + :param version: The specific version id of the Index to retrieve. Required. :type version: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -4178,9 +4561,9 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) + cls: ClsType[_models.Index] = kwargs.pop("cls", None) - _request = build_datasets_get_request( + _request = build_indexes_get_request( name=name, version=version, api_version=self._config.api_version, @@ -4212,7 +4595,7 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DatasetVersion, response.json()) + deserialized = _deserialize(_models.Index, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -4221,12 +4604,12 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.DatasetVersion: @distributed_trace def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements - """Delete the specific version of the DatasetVersion. The service returns 204 No Content if the - DatasetVersion was deleted successfully or if the DatasetVersion does not exist. + """Delete the specific version of the Index. The service returns 204 No Content if the Index was + deleted successfully or if the Index does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The version of the DatasetVersion to delete. Required. + :param version: The version of the Index to delete. Required. :type version: str :return: None :rtype: None @@ -4245,7 +4628,7 @@ def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: dis cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_datasets_delete_request( + _request = build_indexes_delete_request( name=name, version=version, api_version=self._config.api_version, @@ -4276,50 +4659,44 @@ def create_or_update( self, name: str, version: str, - dataset_version: _models.DatasetVersion, + index: _models.Index, *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Required. - :type dataset_version: ~azure.ai.projects.models.DatasetVersion + :param index: The Index to create or update. Required. + :type index: ~azure.ai.projects.models.Index :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ @overload def create_or_update( - self, - name: str, - version: str, - dataset_version: JSON, - *, - content_type: str = "application/merge-patch+json", - **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + self, name: str, version: str, index: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Required. - :type dataset_version: JSON + :param index: The Index to create or update. Required. + :type index: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ @@ -4328,42 +4705,42 @@ def create_or_update( self, name: str, version: str, - dataset_version: IO[bytes], + index: IO[bytes], *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Required. - :type dataset_version: IO[bytes] + :param index: The Index to create or update. Required. + :type index: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/merge-patch+json". :paramtype content_type: str - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace def create_or_update( - self, name: str, version: str, dataset_version: Union[_models.DatasetVersion, JSON, IO[bytes]], **kwargs: Any - ) -> _models.DatasetVersion: - """Create a new or update an existing DatasetVersion with the given version id. - - :param name: The name of the resource. Required. + self, name: str, version: str, index: Union[_models.Index, JSON, IO[bytes]], **kwargs: Any + ) -> _models.Index: + """Create a new or update an existing Index with the given version id. + + :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to create or update. Required. + :param version: The specific version id of the Index to create or update. Required. :type version: str - :param dataset_version: The DatasetVersion to create or update. Is one of the following types: - DatasetVersion, JSON, IO[bytes] Required. - :type dataset_version: ~azure.ai.projects.models.DatasetVersion or JSON or IO[bytes] - :return: DatasetVersion. The DatasetVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetVersion + :param index: The Index to create or update. Is one of the following types: Index, JSON, + IO[bytes] Required. + :type index: ~azure.ai.projects.models.Index or JSON or IO[bytes] + :return: Index. The Index is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Index :raises ~azure.core.exceptions.HttpResponseError: """ error_map: MutableMapping = { @@ -4378,16 +4755,16 @@ def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.DatasetVersion] = kwargs.pop("cls", None) + cls: ClsType[_models.Index] = kwargs.pop("cls", None) content_type = content_type or "application/merge-patch+json" _content = None - if isinstance(dataset_version, (IOBase, bytes)): - _content = dataset_version + if isinstance(index, (IOBase, bytes)): + _content = index else: - _content = json.dumps(dataset_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(index, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_datasets_create_or_update_request( + _request = build_indexes_create_or_update_request( name=name, version=version, content_type=content_type, @@ -4421,113 +4798,44 @@ def create_or_update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.DatasetVersion, response.json()) + deserialized = _deserialize(_models.Index, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - def pending_upload( - self, - name: str, - version: str, - pending_upload_request: _models.PendingUploadRequest, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def pending_upload( - self, - name: str, - version: str, - pending_upload_request: JSON, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ +class BetaEvaluationTaxonomiesOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - @overload - def pending_upload( - self, - name: str, - version: str, - pending_upload_request: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`evaluation_taxonomies` attribute. + """ - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def pending_upload( - self, - name: str, - version: str, - pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of a dataset for a specific version. + def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: + """Get an evaluation run by name. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Is one of the following - types: PendingUploadRequest, JSON, IO[bytes] Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or - IO[bytes] - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4536,25 +4844,15 @@ def pending_upload( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) - - content_type = content_type or "application/json" - _content = None - if isinstance(pending_upload_request, (IOBase, bytes)): - _content = pending_upload_request - else: - _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) - _request = build_datasets_pending_upload_request( + _request = build_beta_evaluation_taxonomies_get_request( name=name, - version=version, - content_type=content_type, + foundry_features=_foundry_features, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -4583,7 +4881,7 @@ def pending_upload( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.PendingUploadResponse, response.json()) + deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -4591,17 +4889,27 @@ def pending_upload( return deserialized # type: ignore @distributed_trace - def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with a Dataset version. + def list( + self, *, input_name: Optional[str] = None, input_type: Optional[str] = None, **kwargs: Any + ) -> ItemPaged["_models.EvaluationTaxonomy"]: + """List evaluation taxonomies. - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the DatasetVersion to operate on. Required. - :type version: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential + :keyword input_name: Filter by the evaluation input name. Default value is None. + :paramtype input_name: str + :keyword input_type: Filter by taxonomy input type. Default value is None. + :paramtype input_type: str + :return: An iterator like instance of EvaluationTaxonomy + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluationTaxonomy] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.EvaluationTaxonomy]] = kwargs.pop("cls", None) + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4610,78 +4918,200 @@ def get_credentials(self, name: str, version: str, **kwargs: Any) -> _models.Dat } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} + def prepare_request(next_link=None): + if not next_link: - cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) + _request = build_beta_evaluation_taxonomies_list_request( + foundry_features=_foundry_features, + input_name=input_name, + input_type=input_type, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _request = build_datasets_get_credentials_request( - name=name, - version=version, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + else: + # make call to next link with the client's api-version + _parsed_next_link = urllib.parse.urlparse(next_link) + _next_request_params = case_insensitive_dict( + { + key: [urllib.parse.quote(v) for v in value] + for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() + } + ) + _next_request_params["api-version"] = self._config.api_version + _request = HttpRequest( + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + ) + path_format_arguments = { + "endpoint": self._serialize.url( + "self._config.endpoint", self._config.endpoint, "str", skip_quote=True + ), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) + return _request - response = pipeline_response.http_response + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.EvaluationTaxonomy], + deserialized.get("value", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("nextLink") or None, iter(list_of_elem) - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass + def get_next(next_link=None): + _request = prepare_request(next_link) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + return pipeline_response + + return ItemPaged(get_next, extract_data) + + @distributed_trace + def delete(self, name: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements + """Delete an evaluation taxonomy by name. + + :param name: The name of the resource. Required. + :type name: str + :return: None + :rtype: None + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[None] = kwargs.pop("cls", None) + + _request = build_beta_evaluation_taxonomies_delete_request( + name=name, + foundry_features=_foundry_features, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.DatasetCredential, response.json()) - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return cls(pipeline_response, None, {}) # type: ignore - return deserialized # type: ignore + @overload + def create( + self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ -class DeploymentsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + @overload + def create( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`deployments` attribute. - """ + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @overload + def create( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. + + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace - def get(self, name: str, **kwargs: Any) -> _models.Deployment: - """Get a deployed model. + def create( + self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Create an evaluation taxonomy. - :param name: Name of the deployment. Required. + :param name: The name of the evaluation taxonomy. Required. :type name: str - :return: Deployment. The Deployment is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Deployment + :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, + IO[bytes] Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4690,14 +5120,25 @@ def get(self, name: str, **kwargs: Any) -> _models.Deployment: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Deployment] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) - _request = build_deployments_get_request( + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluation_taxonomies_create_request( name=name, + foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -4714,7 +5155,7 @@ def get(self, name: str, **kwargs: Any) -> _models.Deployment: response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [200, 201]: if _stream: try: response.read() # Load the body in memory and close the socket @@ -4723,133 +5164,159 @@ def get(self, name: str, **kwargs: Any) -> _models.Deployment: map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) - response_headers = {} - response_headers["x-ms-client-request-id"] = self._deserialize( - "str", response.headers.get("x-ms-client-request-id") - ) - if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Deployment, response.json()) + deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace - def list( - self, - *, - model_publisher: Optional[str] = None, - model_name: Optional[str] = None, - deployment_type: Optional[Union[str, _models.DeploymentType]] = None, - **kwargs: Any - ) -> ItemPaged["_models.Deployment"]: - """List all deployed models in the project. + @overload + def update( + self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - :keyword model_publisher: Model publisher to filter models by. Default value is None. - :paramtype model_publisher: str - :keyword model_name: Model name (the publisher specific name) to filter models by. Default - value is None. - :paramtype model_name: str - :keyword deployment_type: Type of deployment to filter list by. "ModelDeployment" Default value - is None. - :paramtype deployment_type: str or ~azure.ai.projects.models.DeploymentType - :return: An iterator like instance of Deployment - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Deployment] + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Deployment]] = kwargs.pop("cls", None) + @overload + def update( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ - def prepare_request(next_link=None): - if not next_link: + @overload + def update( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - _request = build_deployments_list_request( - model_publisher=model_publisher, - model_name=model_name, - deployment_type=deployment_type, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + @distributed_trace + def update( + self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationTaxonomy: + """Update an evaluation taxonomy. - return _request + :param name: The name of the evaluation taxonomy. Required. + :type name: str + :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, + IO[bytes] Required. + :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] + :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.Deployment], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, iter(list_of_elem) + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - def get_next(next_link=None): - _request = prepare_request(next_link) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) - _stream = False - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + _request = build_beta_evaluation_taxonomies_update_request( + name=name, + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - return pipeline_response + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) - return ItemPaged(get_next, extract_data) + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) -class IndexesOperations: + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + +class BetaEvaluatorsOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`indexes` attribute. + :attr:`evaluators` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -4860,19 +5327,36 @@ def __init__(self, *args, **kwargs) -> None: self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def list_versions(self, name: str, **kwargs: Any) -> ItemPaged["_models.Index"]: - """List all versions of the given Index. + def list_versions( + self, + name: str, + *, + type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, + limit: Optional[int] = None, + **kwargs: Any + ) -> ItemPaged["_models.EvaluatorVersion"]: + """List all versions of the given evaluator. :param name: The name of the resource. Required. :type name: str - :return: An iterator like instance of Index - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Index] + :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one + of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default + value is None. + :paramtype type: str or str or str or str + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. Default value is None. + :paramtype limit: int + :return: An iterator like instance of EvaluatorVersion + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4885,8 +5369,11 @@ def list_versions(self, name: str, **kwargs: Any) -> ItemPaged["_models.Index"]: def prepare_request(next_link=None): if not next_link: - _request = build_indexes_list_versions_request( + _request = build_beta_evaluators_list_versions_request( name=name, + foundry_features=_foundry_features, + type=type, + limit=limit, api_version=self._config.api_version, headers=_headers, params=_params, @@ -4909,7 +5396,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -4923,7 +5413,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Index], + List[_models.EvaluatorVersion], deserialized.get("value", []), ) if cls: @@ -4948,17 +5438,33 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) @distributed_trace - def list(self, **kwargs: Any) -> ItemPaged["_models.Index"]: - """List the latest version of each Index. + def list( + self, + *, + type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, + limit: Optional[int] = None, + **kwargs: Any + ) -> ItemPaged["_models.EvaluatorVersion"]: + """List the latest version of each evaluator. - :return: An iterator like instance of Index - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Index] + :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one + of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default + value is None. + :paramtype type: str or str or str or str + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the default is 20. Default value is None. + :paramtype limit: int + :return: An iterator like instance of EvaluatorVersion + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Index]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -4971,7 +5477,10 @@ def list(self, **kwargs: Any) -> ItemPaged["_models.Index"]: def prepare_request(next_link=None): if not next_link: - _request = build_indexes_list_request( + _request = build_beta_evaluators_list_request( + foundry_features=_foundry_features, + type=type, + limit=limit, api_version=self._config.api_version, headers=_headers, params=_params, @@ -4994,7 +5503,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5008,7 +5520,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Index], + List[_models.EvaluatorVersion], deserialized.get("value", []), ) if cls: @@ -5033,18 +5545,21 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) @distributed_trace - def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: - """Get the specific version of the Index. The service returns 404 Not Found error if the Index - does not exist. + def get_version(self, name: str, version: str, **kwargs: Any) -> _models.EvaluatorVersion: + """Get the specific version of the EvaluatorVersion. The service returns 404 Not Found error if + the EvaluatorVersion does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to retrieve. Required. + :param version: The specific version id of the EvaluatorVersion to retrieve. Required. :type version: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5056,11 +5571,12 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Index] = kwargs.pop("cls", None) + cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - _request = build_indexes_get_request( + _request = build_beta_evaluators_get_version_request( name=name, version=version, + foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5090,7 +5606,7 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Index, response.json()) + deserialized = _deserialize(_models.EvaluatorVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -5098,18 +5614,23 @@ def get(self, name: str, version: str, **kwargs: Any) -> _models.Index: return deserialized # type: ignore @distributed_trace - def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements - """Delete the specific version of the Index. The service returns 204 No Content if the Index was - deleted successfully or if the Index does not exist. + def delete_version( # pylint: disable=inconsistent-return-statements + self, name: str, version: str, **kwargs: Any + ) -> None: + """Delete the specific version of the EvaluatorVersion. The service returns 204 No Content if the + EvaluatorVersion was deleted successfully or if the EvaluatorVersion does not exist. :param name: The name of the resource. Required. :type name: str - :param version: The version of the Index to delete. Required. + :param version: The version of the EvaluatorVersion to delete. Required. :type version: str :return: None :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5123,9 +5644,10 @@ def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: dis cls: ClsType[None] = kwargs.pop("cls", None) - _request = build_indexes_delete_request( + _request = build_beta_evaluators_delete_version_request( name=name, version=version, + foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5150,94 +5672,82 @@ def delete(self, name: str, version: str, **kwargs: Any) -> None: # pylint: dis return cls(pipeline_response, None, {}) # type: ignore @overload - def create_or_update( + def create_version( self, name: str, - version: str, - index: _models.Index, + evaluator_version: _models.EvaluatorVersion, *, - content_type: str = "application/merge-patch+json", + content_type: str = "application/json", **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Required. - :type index: ~azure.ai.projects.models.Index + :param evaluator_version: Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/merge-patch+json". + Default value is "application/json". :paramtype content_type: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_or_update( - self, name: str, version: str, index: JSON, *, content_type: str = "application/merge-patch+json", **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + def create_version( + self, name: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Required. - :type index: JSON + :param evaluator_version: Required. + :type evaluator_version: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/merge-patch+json". + Default value is "application/json". :paramtype content_type: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_or_update( - self, - name: str, - version: str, - index: IO[bytes], - *, - content_type: str = "application/merge-patch+json", - **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + def create_version( + self, name: str, evaluator_version: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Required. - :type index: IO[bytes] + :param evaluator_version: Required. + :type evaluator_version: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/merge-patch+json". + Default value is "application/json". :paramtype content_type: str - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create_or_update( - self, name: str, version: str, index: Union[_models.Index, JSON, IO[bytes]], **kwargs: Any - ) -> _models.Index: - """Create a new or update an existing Index with the given version id. + def create_version( + self, name: str, evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluatorVersion: + """Create a new EvaluatorVersion with auto incremented version id. :param name: The name of the resource. Required. :type name: str - :param version: The specific version id of the Index to create or update. Required. - :type version: str - :param index: The Index to create or update. Is one of the following types: Index, JSON, - IO[bytes] Required. - :type index: ~azure.ai.projects.models.Index or JSON or IO[bytes] - :return: Index. The Index is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Index + :param evaluator_version: Is one of the following types: EvaluatorVersion, JSON, IO[bytes] + Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5250,18 +5760,18 @@ def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.Index] = kwargs.pop("cls", None) + cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - content_type = content_type or "application/merge-patch+json" + content_type = content_type or "application/json" _content = None - if isinstance(index, (IOBase, bytes)): - _content = index + if isinstance(evaluator_version, (IOBase, bytes)): + _content = evaluator_version else: - _content = json.dumps(index, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_indexes_create_or_update_request( + _request = build_beta_evaluators_create_version_request( name=name, - version=version, + foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5281,7 +5791,7 @@ def create_or_update( response = pipeline_response.http_response - if response.status_code not in [200, 201]: + if response.status_code not in [201]: if _stream: try: response.read() # Load the body in memory and close the socket @@ -5293,39 +5803,104 @@ def create_or_update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Index, response.json()) + deserialized = _deserialize(_models.EvaluatorVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + @overload + def update_version( + self, + name: str, + version: str, + evaluator_version: _models.EvaluatorVersion, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. -class BetaEvaluationTaxonomiesOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`evaluation_taxonomies` attribute. - """ + @overload + def update_version( + self, name: str, version: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Required. + :type evaluator_version: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def update_version( + self, + name: str, + version: str, + evaluator_version: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. + + :param name: The name of the resource. Required. + :type name: str + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Required. + :type evaluator_version: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace - def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: - """Get an evaluation run by name. + def update_version( + self, + name: str, + version: str, + evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.EvaluatorVersion: + """Update an existing EvaluatorVersion with the given version id. :param name: The name of the resource. Required. :type name: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The version of the EvaluatorVersion to update. Required. + :type version: str + :param evaluator_version: Evaluator resource. Is one of the following types: EvaluatorVersion, + JSON, IO[bytes] Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( @@ -5339,15 +5914,26 @@ def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - _request = build_beta_evaluation_taxonomies_get_request( + content_type = content_type or "application/json" + _content = None + if isinstance(evaluator_version, (IOBase, bytes)): + _content = evaluator_version + else: + _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_evaluators_update_version_request( name=name, + version=version, foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -5376,232 +5962,111 @@ def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) + deserialized = _deserialize(_models.EvaluatorVersion, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @distributed_trace - def list( - self, *, input_name: Optional[str] = None, input_type: Optional[str] = None, **kwargs: Any - ) -> ItemPaged["_models.EvaluationTaxonomy"]: - """List evaluation taxonomies. - - :keyword input_name: Filter by the evaluation input name. Default value is None. - :paramtype input_name: str - :keyword input_type: Filter by taxonomy input type. Default value is None. - :paramtype input_type: str - :return: An iterator like instance of EvaluationTaxonomy - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluationTaxonomy] - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[List[_models.EvaluationTaxonomy]] = kwargs.pop("cls", None) - - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - def prepare_request(next_link=None): - if not next_link: - - _request = build_beta_evaluation_taxonomies_list_request( - foundry_features=_foundry_features, - input_name=input_name, - input_type=input_type, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - return _request - - def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.EvaluationTaxonomy], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, iter(list_of_elem) - - def get_next(next_link=None): - _request = prepare_request(next_link) - - _stream = False - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - return pipeline_response - - return ItemPaged(get_next, extract_data) - - @distributed_trace - def delete(self, name: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements - """Delete an evaluation taxonomy by name. + @overload + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: _models.PendingUploadRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. :param name: The name of the resource. Required. :type name: str - :return: None - :rtype: None - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[None] = kwargs.pop("cls", None) - - _request = build_beta_evaluation_taxonomies_delete_request( - name=name, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _stream = False - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [204]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) - - if cls: - return cls(pipeline_response, None, {}) # type: ignore - - @overload - def create( - self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. - - :param name: The name of the evaluation taxonomy. Required. - :type name: str - :param body: The evaluation taxonomy. Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: JSON + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: IO[bytes] + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Required. + :type pending_upload_request: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create( - self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Create an evaluation taxonomy. + def pending_upload( + self, + name: str, + version: str, + pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.PendingUploadResponse: + """Start a new or get an existing pending upload of an evaluator for a specific version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, - IO[bytes] Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param pending_upload_request: The pending upload request parameters. Is one of the following + types: PendingUploadRequest, JSON, IO[bytes] Required. + :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or + IO[bytes] + :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( @@ -5619,17 +6084,18 @@ def create( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) + cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(pending_upload_request, (IOBase, bytes)): + _content = pending_upload_request else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluation_taxonomies_create_request( + _request = build_beta_evaluators_pending_upload_request( name=name, + version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -5650,7 +6116,7 @@ def create( response = pipeline_response.http_response - if response.status_code not in [200, 201]: + if response.status_code not in [200]: if _stream: try: response.read() # Load the body in memory and close the socket @@ -5662,7 +6128,7 @@ def create( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) + deserialized = _deserialize(_models.PendingUploadResponse, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -5670,72 +6136,103 @@ def create( return deserialized # type: ignore @overload - def update( - self, name: str, body: _models.EvaluationTaxonomy, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + def get_credentials( + self, + name: str, + version: str, + credential_request: _models.EvaluatorCredentialRequest, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def update( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + def get_credentials( + self, + name: str, + version: str, + credential_request: JSON, + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: JSON + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def update( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + def get_credentials( + self, + name: str, + version: str, + credential_request: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Required. - :type body: IO[bytes] + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Required. + :type credential_request: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def update( - self, name: str, body: Union[_models.EvaluationTaxonomy, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationTaxonomy: - """Update an evaluation taxonomy. + def get_credentials( + self, + name: str, + version: str, + credential_request: Union[_models.EvaluatorCredentialRequest, JSON, IO[bytes]], + **kwargs: Any + ) -> _models.DatasetCredential: + """Get the SAS credential to access the storage account associated with an Evaluator version. - :param name: The name of the evaluation taxonomy. Required. + :param name: The name of the resource. Required. :type name: str - :param body: The evaluation taxonomy. Is one of the following types: EvaluationTaxonomy, JSON, - IO[bytes] Required. - :type body: ~azure.ai.projects.models.EvaluationTaxonomy or JSON or IO[bytes] - :return: EvaluationTaxonomy. The EvaluationTaxonomy is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationTaxonomy + :param version: The specific version id of the EvaluatorVersion to operate on. Required. + :type version: str + :param credential_request: The credential request parameters. Is one of the following types: + EvaluatorCredentialRequest, JSON, IO[bytes] Required. + :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest or JSON or + IO[bytes] + :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( @@ -5753,17 +6250,18 @@ def update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluationTaxonomy] = kwargs.pop("cls", None) + cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(credential_request, (IOBase, bytes)): + _content = credential_request else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(credential_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluation_taxonomies_update_request( + _request = build_beta_evaluators_get_credentials_request( name=name, + version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -5796,7 +6294,7 @@ def update( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluationTaxonomy, response.json()) + deserialized = _deserialize(_models.DatasetCredential, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -5804,14 +6302,14 @@ def update( return deserialized # type: ignore -class BetaEvaluatorsOperations: +class BetaInsightsOperations: """ .. warning:: **DO NOT** instantiate this class directly. Instead, you should access the following operations through :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`evaluators` attribute. + :attr:`insights` attribute. """ def __init__(self, *args, **kwargs) -> None: @@ -5821,38 +6319,67 @@ def __init__(self, *args, **kwargs) -> None: self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - @distributed_trace - def list_versions( - self, - name: str, - *, - type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, - limit: Optional[int] = None, - **kwargs: Any - ) -> ItemPaged["_models.EvaluatorVersion"]: - """List all versions of the given evaluator. + @overload + def generate( + self, insight: _models.Insight, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Insight: + """Generate Insights. - :param name: The name of the resource. Required. - :type name: str - :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one - of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default - value is None. - :paramtype type: str or str or str or str - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the default is 20. Default value is None. - :paramtype limit: int - :return: An iterator like instance of EvaluatorVersion - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluatorVersion] + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Required. + :type insight: ~azure.ai.projects.models.Insight + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) + @overload + def generate(self, insight: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.Insight: + """Generate Insights. + + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Required. + :type insight: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def generate(self, insight: IO[bytes], *, content_type: str = "application/json", **kwargs: Any) -> _models.Insight: + """Generate Insights. + + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Required. + :type insight: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: Any) -> _models.Insight: + """Generate Insights. + :param insight: Complete evaluation configuration including data source, evaluators, and result + settings. Is one of the following types: Insight, JSON, IO[bytes] Required. + :type insight: ~azure.ai.projects.models.Insight or JSON or IO[bytes] + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5861,105 +6388,164 @@ def list_versions( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(next_link=None): - if not next_link: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - _request = build_beta_evaluators_list_versions_request( - name=name, - foundry_features=_foundry_features, - type=type, - limit=limit, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.Insight] = kwargs.pop("cls", None) - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + content_type = content_type or "application/json" + _content = None + if isinstance(insight, (IOBase, bytes)): + _content = insight + else: + _content = json.dumps(insight, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - return _request + _request = build_beta_insights_generate_request( + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) - def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.EvaluatorVersion], - deserialized.get("value", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, iter(list_of_elem) + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) - def get_next(next_link=None): - _request = prepare_request(next_link) + response = pipeline_response.http_response - _stream = False - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response + if response.status_code not in [201]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Insight, response.json()) - return pipeline_response + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore - return ItemPaged(get_next, extract_data) + return deserialized # type: ignore + + @distributed_trace + def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, **kwargs: Any) -> _models.Insight: + """Get a specific insight by Id. + + :param insight_id: The unique identifier for the insights report. Required. + :type insight_id: str + :keyword include_coordinates: Whether to include coordinates for visualization in the response. + Defaults to false. Default value is None. + :paramtype include_coordinates: bool + :return: Insight. The Insight is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Insight + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.Insight] = kwargs.pop("cls", None) + + _request = build_beta_insights_get_request( + insight_id=insight_id, + foundry_features=_foundry_features, + include_coordinates=include_coordinates, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + raise HttpResponseError(response=response) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Insight, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @distributed_trace def list( self, *, - type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, - limit: Optional[int] = None, + type: Optional[Union[str, _models.InsightType]] = None, + eval_id: Optional[str] = None, + run_id: Optional[str] = None, + agent_name: Optional[str] = None, + include_coordinates: Optional[bool] = None, **kwargs: Any - ) -> ItemPaged["_models.EvaluatorVersion"]: - """List the latest version of each evaluator. + ) -> ItemPaged["_models.Insight"]: + """List all insights in reverse chronological order (newest first). - :keyword type: Filter evaluators by type. Possible values: 'all', 'custom', 'builtin'. Is one - of the following types: Literal["builtin"], Literal["custom"], Literal["all"], str Default - value is None. - :paramtype type: str or str or str or str - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the default is 20. Default value is None. - :paramtype limit: int - :return: An iterator like instance of EvaluatorVersion - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluatorVersion] + :keyword type: Filter by the type of analysis. Known values are: "EvaluationRunClusterInsight", + "AgentClusterInsight", and "EvaluationComparison". Default value is None. + :paramtype type: str or ~azure.ai.projects.models.InsightType + :keyword eval_id: Filter by the evaluation ID. Default value is None. + :paramtype eval_id: str + :keyword run_id: Filter by the evaluation run ID. Default value is None. + :paramtype run_id: str + :keyword agent_name: Filter by the agent name. Default value is None. + :paramtype agent_name: str + :keyword include_coordinates: Whether to include coordinates for visualization in the response. + Defaults to false. Default value is None. + :paramtype include_coordinates: bool + :return: An iterator like instance of Insight + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Insight] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.EvaluatorVersion]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Insight]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -5972,10 +6558,13 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_beta_evaluators_list_request( + _request = build_beta_insights_list_request( foundry_features=_foundry_features, type=type, - limit=limit, + eval_id=eval_id, + run_id=run_id, + agent_name=agent_name, + include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6015,7 +6604,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.EvaluatorVersion], + List[_models.Insight], deserialized.get("value", []), ) if cls: @@ -6039,92 +6628,116 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) - @distributed_trace - def get_version(self, name: str, version: str, **kwargs: Any) -> _models.EvaluatorVersion: - """Get the specific version of the EvaluatorVersion. The service returns 404 Not Found error if - the EvaluatorVersion does not exist. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to retrieve. Required. - :type version: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} +class BetaMemoryStoresOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`memory_stores` attribute. + """ - _request = build_beta_evaluators_get_version_request( - name=name, - version=version, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) + @overload + def create( + self, + *, + name: str, + definition: _models.MemoryStoreDefinition, + content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - response = pipeline_response.http_response + :keyword name: The name of the memory store. Required. + :paramtype name: str + :keyword definition: The memory store definition. Required. + :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + @overload + def create( + self, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.EvaluatorVersion, response.json()) + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + @overload + def create( + self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - return deserialized # type: ignore + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace - def delete_version( # pylint: disable=inconsistent-return-statements - self, name: str, version: str, **kwargs: Any - ) -> None: - """Delete the specific version of the EvaluatorVersion. The service returns 204 No Content if the - EvaluatorVersion was deleted successfully or if the EvaluatorVersion does not exist. + def create( + self, + body: Union[JSON, IO[bytes]] = _Unset, + *, + name: str = _Unset, + definition: _models.MemoryStoreDefinition = _Unset, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Create a memory store. - :param name: The name of the resource. Required. - :type name: str - :param version: The version of the EvaluatorVersion to delete. Required. - :type version: str - :return: None - :rtype: None + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword name: The name of the memory store. Required. + :paramtype name: str + :keyword definition: The memory store definition. Required. + :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6134,16 +6747,31 @@ def delete_version( # pylint: disable=inconsistent-return-statements } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[None] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - _request = build_beta_evaluators_delete_version_request( - name=name, - version=version, + if body is _Unset: + if name is _Unset: + raise TypeError("missing required argument: name") + if definition is _Unset: + raise TypeError("missing required argument: definition") + body = {"definition": definition, "description": description, "metadata": metadata, "name": name} + body = {k: v for k, v in body.items() if v is not None} + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_memory_stores_create_request( foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -6152,96 +6780,127 @@ def delete_version( # pylint: disable=inconsistent-return-statements } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [204]: + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @overload - def create_version( + def update( self, name: str, - evaluator_version: _models.EvaluatorVersion, *, content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_version( - self, name: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + def update( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Required. - :type evaluator_version: JSON + :param body: Required. + :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_version( - self, name: str, evaluator_version: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + def update( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Required. - :type evaluator_version: IO[bytes] + :param body: Required. + :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create_version( - self, name: str, evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluatorVersion: - """Create a new EvaluatorVersion with auto incremented version id. + def update( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.MemoryStoreDetails: + """Update a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to update. Required. :type name: str - :param evaluator_version: Is one of the following types: EvaluatorVersion, JSON, IO[bytes] - Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword description: A human-readable description of the memory store. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default + value is None. + :paramtype metadata: dict[str, str] + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6255,16 +6914,19 @@ def create_version( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) + cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) + if body is _Unset: + body = {"description": description, "metadata": metadata} + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(evaluator_version, (IOBase, bytes)): - _content = evaluator_version + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluators_create_version_request( + _request = build_beta_memory_stores_update_request( name=name, foundry_features=_foundry_features, content_type=content_type, @@ -6286,120 +6948,204 @@ def create_version( response = pipeline_response.http_response - if response.status_code not in [201]: + if response.status_code not in [200]: if _stream: try: response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluatorVersion, response.json()) + deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - def update_version( - self, - name: str, - version: str, - evaluator_version: _models.EvaluatorVersion, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + @distributed_trace + def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: + """Retrieve a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to retrieve. Required. :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) - @overload - def update_version( - self, name: str, version: str, evaluator_version: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} - :param name: The name of the resource. Required. - :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Required. - :type evaluator_version: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: - """ + cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - @overload - def update_version( + _request = build_beta_memory_stores_get_request( + name=name, + foundry_features=_foundry_features, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + def list( self, - name: str, - version: str, - evaluator_version: IO[bytes], *, - content_type: str = "application/json", + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + before: Optional[str] = None, **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + ) -> ItemPaged["_models.MemoryStoreDetails"]: + """List all memory stores. - :param name: The name of the resource. Required. - :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Required. - :type evaluator_version: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the + default is 20. Default value is None. + :paramtype limit: int + :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for + ascending order and``desc`` + for descending order. Known values are: "asc" and "desc". Default value is None. + :paramtype order: str or ~azure.ai.projects.models.PageOrder + :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your + place in the list. + For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page of the list. + Default value is None. + :paramtype before: str + :return: An iterator like instance of MemoryStoreDetails + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.MemoryStoreDetails] :raises ~azure.core.exceptions.HttpResponseError: """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + ) + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[List[_models.MemoryStoreDetails]] = kwargs.pop("cls", None) + + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + def prepare_request(_continuation_token=None): + + _request = build_beta_memory_stores_list_request( + foundry_features=_foundry_features, + limit=limit, + order=order, + after=_continuation_token, + before=before, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + return _request + + def extract_data(pipeline_response): + deserialized = pipeline_response.http_response.json() + list_of_elem = _deserialize( + List[_models.MemoryStoreDetails], + deserialized.get("data", []), + ) + if cls: + list_of_elem = cls(list_of_elem) # type: ignore + return deserialized.get("last_id") or None, iter(list_of_elem) + + def get_next(_continuation_token=None): + _request = prepare_request(_continuation_token) + + _stream = False + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + response = pipeline_response.http_response + + if response.status_code not in [200]: + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + return pipeline_response + + return ItemPaged(get_next, extract_data) @distributed_trace - def update_version( - self, - name: str, - version: str, - evaluator_version: Union[_models.EvaluatorVersion, JSON, IO[bytes]], - **kwargs: Any - ) -> _models.EvaluatorVersion: - """Update an existing EvaluatorVersion with the given version id. + def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreResult: + """Delete a memory store. - :param name: The name of the resource. Required. + :param name: The name of the memory store to delete. Required. :type name: str - :param version: The version of the EvaluatorVersion to update. Required. - :type version: str - :param evaluator_version: Evaluator resource. Is one of the following types: EvaluatorVersion, - JSON, IO[bytes] Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :return: EvaluatorVersion. The EvaluatorVersion is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluatorVersion + :return: DeleteMemoryStoreResult. The DeleteMemoryStoreResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DeleteMemoryStoreResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6409,26 +7155,15 @@ def update_version( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.EvaluatorVersion] = kwargs.pop("cls", None) - - content_type = content_type or "application/json" - _content = None - if isinstance(evaluator_version, (IOBase, bytes)): - _content = evaluator_version - else: - _content = json.dumps(evaluator_version, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + cls: ClsType[_models.DeleteMemoryStoreResult] = kwargs.pop("cls", None) - _request = build_beta_evaluators_update_version_request( + _request = build_beta_memory_stores_delete_request( name=name, - version=version, foundry_features=_foundry_features, - content_type=content_type, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -6452,12 +7187,16 @@ def update_version( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.EvaluatorVersion, response.json()) + deserialized = _deserialize(_models.DeleteMemoryStoreResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -6465,107 +7204,60 @@ def update_version( return deserialized # type: ignore @overload - def pending_upload( + def _search_memories( self, name: str, - version: str, - pending_upload_request: _models.PendingUploadRequest, *, + scope: str, content_type: str = "application/json", + items: Optional[List[dict[str, Any]]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def pending_upload( - self, - name: str, - version: str, - pending_upload_request: JSON, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def pending_upload( - self, - name: str, - version: str, - pending_upload_request: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Required. - :type pending_upload_request: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse - :raises ~azure.core.exceptions.HttpResponseError: - """ + ) -> _models.MemoryStoreSearchResult: ... + @overload + def _search_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: ... + @overload + def _search_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: ... @distributed_trace - def pending_upload( + def _search_memories( self, name: str, - version: str, - pending_upload_request: Union[_models.PendingUploadRequest, JSON, IO[bytes]], + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[List[dict[str, Any]]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, **kwargs: Any - ) -> _models.PendingUploadResponse: - """Start a new or get an existing pending upload of an evaluator for a specific version. + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. - :param name: The name of the resource. Required. + :param name: The name of the memory store to search. Required. :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param pending_upload_request: The pending upload request parameters. Is one of the following - types: PendingUploadRequest, JSON, IO[bytes] Required. - :type pending_upload_request: ~azure.ai.projects.models.PendingUploadRequest or JSON or - IO[bytes] - :return: PendingUploadResponse. The PendingUploadResponse is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.PendingUploadResponse + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: Items for which to search for relevant memories. Default value is None. + :paramtype items: list[dict[str, any]] + :keyword previous_search_id: The unique ID of the previous search request, enabling incremental + memory search from where the last operation left off. Default value is None. + :paramtype previous_search_id: str + :keyword options: Memory search options. Default value is None. + :paramtype options: ~azure.ai.projects.models.MemorySearchOptions + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6579,18 +7271,27 @@ def pending_upload( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.PendingUploadResponse] = kwargs.pop("cls", None) + cls: ClsType[_models.MemoryStoreSearchResult] = kwargs.pop("cls", None) + if body is _Unset: + if scope is _Unset: + raise TypeError("missing required argument: scope") + body = { + "items": items, + "options": options, + "previous_search_id": previous_search_id, + "scope": scope, + } + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(pending_upload_request, (IOBase, bytes)): - _content = pending_upload_request + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(pending_upload_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluators_pending_upload_request( + _request = build_beta_memory_stores_search_memories_request( name=name, - version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -6618,120 +7319,35 @@ def pending_upload( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.PendingUploadResponse, response.json()) + deserialized = _deserialize(_models.MemoryStoreSearchResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - def get_credentials( - self, - name: str, - version: str, - credential_request: _models.EvaluatorCredentialRequest, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Required. - :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def get_credentials( - self, - name: str, - version: str, - credential_request: JSON, - *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Required. - :type credential_request: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def get_credentials( + def _update_memories_initial( self, name: str, - version: str, - credential_request: IO[bytes], + body: Union[JSON, IO[bytes]] = _Unset, *, - content_type: str = "application/json", - **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Required. - :type credential_request: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - def get_credentials( - self, - name: str, - version: str, - credential_request: Union[_models.EvaluatorCredentialRequest, JSON, IO[bytes]], + scope: str = _Unset, + items: Optional[List[dict[str, Any]]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, **kwargs: Any - ) -> _models.DatasetCredential: - """Get the SAS credential to access the storage account associated with an Evaluator version. - - :param name: The name of the resource. Required. - :type name: str - :param version: The specific version id of the EvaluatorVersion to operate on. Required. - :type version: str - :param credential_request: The credential request parameters. Is one of the following types: - EvaluatorCredentialRequest, JSON, IO[bytes] Required. - :type credential_request: ~azure.ai.projects.models.EvaluatorCredentialRequest or JSON or - IO[bytes] - :return: DatasetCredential. The DatasetCredential is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DatasetCredential - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW + ) -> Iterator[bytes]: + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6745,18 +7361,27 @@ def get_credentials( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.DatasetCredential] = kwargs.pop("cls", None) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + if body is _Unset: + if scope is _Unset: + raise TypeError("missing required argument: scope") + body = { + "items": items, + "previous_update_id": previous_update_id, + "scope": scope, + "update_delay": update_delay, + } + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(credential_request, (IOBase, bytes)): - _content = credential_request + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(credential_request, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_evaluators_get_credentials_request( + _request = build_beta_memory_stores_update_memories_request( name=name, - version=version, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -6770,110 +7395,236 @@ def get_credentials( _request.url = self._client.format_url(_request.url, **path_format_arguments) _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) + _stream = True pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass + if response.status_code not in [202]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.DatasetCredential, response.json()) + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore + return cls(pipeline_response, deserialized, response_headers) # type: ignore return deserialized # type: ignore + @overload + def _begin_update_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[List[dict[str, Any]]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any + ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: ... + @overload + def _begin_update_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: ... + @overload + def _begin_update_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: ... -class BetaInsightsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. + @distributed_trace + def _begin_update_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[List[dict[str, Any]]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any + ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: + """Update memory store with conversation memories. - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`insights` attribute. - """ + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: Conversation items to be stored in memory. Default value is None. + :paramtype items: list[dict[str, any]] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of LROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.core.polling.LROPoller[~azure.ai.projects.models.MemoryStoreUpdateCompletedResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + ) + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._update_memories_initial( + name=name, + body=body, + foundry_features=_foundry_features, + scope=scope, + items=items, + previous_update_id=previous_update_id, + update_delay=update_delay, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.MemoryStoreUpdateCompletedResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.MemoryStoreUpdateCompletedResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.MemoryStoreUpdateCompletedResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) @overload - def generate( - self, insight: _models.Insight, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Insight: - """Generate Insights. + def delete_scope( + self, name: str, *, scope: str, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Required. - :type insight: ~azure.ai.projects.models.Insight + :param name: The name of the memory store. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories to delete, such as a + user ID. Required. + :paramtype scope: str :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def generate(self, insight: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.Insight: - """Generate Insights. + def delete_scope( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Required. - :type insight: JSON + :param name: The name of the memory store. Required. + :type name: str + :param body: Required. + :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def generate(self, insight: IO[bytes], *, content_type: str = "application/json", **kwargs: Any) -> _models.Insight: - """Generate Insights. + def delete_scope( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Required. - :type insight: IO[bytes] + :param name: The name of the memory store. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: Any) -> _models.Insight: - """Generate Insights. + def delete_scope( + self, name: str, body: Union[JSON, IO[bytes]] = _Unset, *, scope: str = _Unset, **kwargs: Any + ) -> _models.MemoryStoreDeleteScopeResult: + """Delete all memories associated with a specific scope from a memory store. - :param insight: Complete evaluation configuration including data source, evaluators, and result - settings. Is one of the following types: Insight, JSON, IO[bytes] Required. - :type insight: ~azure.ai.projects.models.Insight or JSON or IO[bytes] - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :param name: The name of the memory store. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories to delete, such as a + user ID. Required. + :paramtype scope: str + :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with + MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6887,16 +7638,22 @@ def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: A _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.Insight] = kwargs.pop("cls", None) + cls: ClsType[_models.MemoryStoreDeleteScopeResult] = kwargs.pop("cls", None) - content_type = content_type or "application/json" - _content = None - if isinstance(insight, (IOBase, bytes)): - _content = insight + if body is _Unset: + if scope is _Unset: + raise TypeError("missing required argument: scope") + body = {"scope": scope} + body = {k: v for k, v in body.items() if v is not None} + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(insight, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_insights_generate_request( + _request = build_beta_memory_stores_delete_scope_request( + name=name, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -6917,40 +7674,59 @@ def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: A response = pipeline_response.http_response - if response.status_code not in [201]: + if response.status_code not in [200]: if _stream: try: response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Insight, response.json()) + deserialized = _deserialize(_models.MemoryStoreDeleteScopeResult, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore + +class BetaRedTeamsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`red_teams` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @distributed_trace - def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, **kwargs: Any) -> _models.Insight: - """Get a specific insight by Id. + def get(self, name: str, **kwargs: Any) -> _models.RedTeam: + """Get a redteam by name. - :param insight_id: The unique identifier for the insights report. Required. - :type insight_id: str - :keyword include_coordinates: Whether to include coordinates for visualization in the response. - Defaults to false. Default value is None. - :paramtype include_coordinates: bool - :return: Insight. The Insight is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Insight + :param name: Identifier of the red team run. Required. + :type name: str + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -6963,12 +7739,11 @@ def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, ** _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Insight] = kwargs.pop("cls", None) + cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) - _request = build_beta_insights_get_request( - insight_id=insight_id, + _request = build_beta_red_teams_get_request( + name=name, foundry_features=_foundry_features, - include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6998,7 +7773,7 @@ def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, ** if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Insight, response.json()) + deserialized = _deserialize(_models.RedTeam, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -7006,41 +7781,20 @@ def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, ** return deserialized # type: ignore @distributed_trace - def list( - self, - *, - type: Optional[Union[str, _models.InsightType]] = None, - eval_id: Optional[str] = None, - run_id: Optional[str] = None, - agent_name: Optional[str] = None, - include_coordinates: Optional[bool] = None, - **kwargs: Any - ) -> ItemPaged["_models.Insight"]: - """List all insights in reverse chronological order (newest first). + def list(self, **kwargs: Any) -> ItemPaged["_models.RedTeam"]: + """List a redteam by name. - :keyword type: Filter by the type of analysis. Known values are: "EvaluationRunClusterInsight", - "AgentClusterInsight", and "EvaluationComparison". Default value is None. - :paramtype type: str or ~azure.ai.projects.models.InsightType - :keyword eval_id: Filter by the evaluation ID. Default value is None. - :paramtype eval_id: str - :keyword run_id: Filter by the evaluation run ID. Default value is None. - :paramtype run_id: str - :keyword agent_name: Filter by the agent name. Default value is None. - :paramtype agent_name: str - :keyword include_coordinates: Whether to include coordinates for visualization in the response. - Defaults to false. Default value is None. - :paramtype include_coordinates: bool - :return: An iterator like instance of Insight - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Insight] + :return: An iterator like instance of RedTeam + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.RedTeam] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Insight]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.RedTeam]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -7053,13 +7807,8 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_beta_insights_list_request( + _request = build_beta_red_teams_list_request( foundry_features=_foundry_features, - type=type, - eval_id=eval_id, - run_id=run_id, - agent_name=agent_name, - include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, params=_params, @@ -7099,7 +7848,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Insight], + List[_models.RedTeam], deserialized.get("value", []), ) if cls: @@ -7123,116 +7872,63 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) - -class BetaMemoryStoresOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`memory_stores` attribute. - """ - - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") - @overload def create( - self, - *, - name: str, - definition: _models.MemoryStoreDefinition, - content_type: str = "application/json", - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + self, red_team: _models.RedTeam, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.RedTeam: + """Creates a redteam run. - :keyword name: The name of the memory store. Required. - :paramtype name: str - :keyword definition: The memory store definition. Required. - :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition + :param red_team: Redteam to be run. Required. + :type red_team: ~azure.ai.projects.models.RedTeam :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create( - self, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + def create(self, red_team: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.RedTeam: + """Creates a redteam run. - :param body: Required. - :type body: JSON + :param red_team: Redteam to be run. Required. + :type red_team: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create( - self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + def create(self, red_team: IO[bytes], *, content_type: str = "application/json", **kwargs: Any) -> _models.RedTeam: + """Creates a redteam run. - :param body: Required. - :type body: IO[bytes] + :param red_team: Redteam to be run. Required. + :type red_team: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create( - self, - body: Union[JSON, IO[bytes]] = _Unset, - *, - name: str = _Unset, - definition: _models.MemoryStoreDefinition = _Unset, - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Create a memory store. + def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwargs: Any) -> _models.RedTeam: + """Creates a redteam run. - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword name: The name of the memory store. Required. - :paramtype name: str - :keyword definition: The memory store definition. Required. - :paramtype definition: ~azure.ai.projects.models.MemoryStoreDefinition - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails + :param red_team: Redteam to be run. Is one of the following types: RedTeam, JSON, IO[bytes] + Required. + :type red_team: ~azure.ai.projects.models.RedTeam or JSON or IO[bytes] + :return: RedTeam. The RedTeam is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -7246,23 +7942,16 @@ def create( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) + cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) - if body is _Unset: - if name is _Unset: - raise TypeError("missing required argument: name") - if definition is _Unset: - raise TypeError("missing required argument: definition") - body = {"definition": definition, "description": description, "metadata": metadata, "name": name} - body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(body, (IOBase, bytes)): - _content = body + if isinstance(red_team, (IOBase, bytes)): + _content = red_team else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(red_team, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_memory_stores_create_request( + _request = build_beta_red_teams_create_request( foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -7283,7 +7972,7 @@ def create( response = pipeline_response.http_response - if response.status_code not in [200]: + if response.status_code not in [201]: if _stream: try: response.read() # Load the body in memory and close the socket @@ -7299,827 +7988,43 @@ def create( if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) + deserialized = _deserialize(_models.RedTeam, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore return deserialized # type: ignore - @overload - def update( - self, - name: str, - *, - content_type: str = "application/json", - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - :param name: The name of the memory store to update. Required. - :type name: str - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def update( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def update( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - def update( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - description: Optional[str] = None, - metadata: Optional[dict[str, str]] = None, - **kwargs: Any - ) -> _models.MemoryStoreDetails: - """Update a memory store. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword description: A human-readable description of the memory store. Default value is None. - :paramtype description: str - :keyword metadata: Arbitrary key-value metadata to associate with the memory store. Default - value is None. - :paramtype metadata: dict[str, str] - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - - if body is _Unset: - body = {"description": description, "metadata": metadata} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_memory_stores_update_request( - name=name, - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - @distributed_trace - def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: - """Retrieve a memory store. - - :param name: The name of the memory store to retrieve. Required. - :type name: str - :return: MemoryStoreDetails. The MemoryStoreDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[_models.MemoryStoreDetails] = kwargs.pop("cls", None) - - _request = build_beta_memory_stores_get_request( - name=name, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreDetails, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - @distributed_trace - def list( - self, - *, - limit: Optional[int] = None, - order: Optional[Union[str, _models.PageOrder]] = None, - before: Optional[str] = None, - **kwargs: Any - ) -> ItemPaged["_models.MemoryStoreDetails"]: - """List all memory stores. - - :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and - 100, and the - default is 20. Default value is None. - :paramtype limit: int - :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for - ascending order and``desc`` - for descending order. Known values are: "asc" and "desc". Default value is None. - :paramtype order: str or ~azure.ai.projects.models.PageOrder - :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your - place in the list. - For instance, if you make a list request and receive 100 objects, ending with obj_foo, your - subsequent call can include before=obj_foo in order to fetch the previous page of the list. - Default value is None. - :paramtype before: str - :return: An iterator like instance of MemoryStoreDetails - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.MemoryStoreDetails] - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[List[_models.MemoryStoreDetails]] = kwargs.pop("cls", None) - - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - def prepare_request(_continuation_token=None): - - _request = build_beta_memory_stores_list_request( - foundry_features=_foundry_features, - limit=limit, - order=order, - after=_continuation_token, - before=before, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - return _request - - def extract_data(pipeline_response): - deserialized = pipeline_response.http_response.json() - list_of_elem = _deserialize( - List[_models.MemoryStoreDetails], - deserialized.get("data", []), - ) - if cls: - list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("last_id") or None, iter(list_of_elem) - - def get_next(_continuation_token=None): - _request = prepare_request(_continuation_token) - - _stream = False - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - response = pipeline_response.http_response - - if response.status_code not in [200]: - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - return pipeline_response - - return ItemPaged(get_next, extract_data) - - @distributed_trace - def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreResult: - """Delete a memory store. - - :param name: The name of the memory store to delete. Required. - :type name: str - :return: DeleteMemoryStoreResult. The DeleteMemoryStoreResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.DeleteMemoryStoreResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = kwargs.pop("headers", {}) or {} - _params = kwargs.pop("params", {}) or {} - - cls: ClsType[_models.DeleteMemoryStoreResult] = kwargs.pop("cls", None) - - _request = build_beta_memory_stores_delete_request( - name=name, - foundry_features=_foundry_features, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.DeleteMemoryStoreResult, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - @overload - def _search_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[List[dict[str, Any]]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any - ) -> _models.MemoryStoreSearchResult: ... - @overload - def _search_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: ... - @overload - def _search_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: ... - - @distributed_trace - def _search_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[List[dict[str, Any]]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: Items for which to search for relevant memories. Default value is None. - :paramtype items: list[dict[str, any]] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreSearchResult] = kwargs.pop("cls", None) - - if body is _Unset: - if scope is _Unset: - raise TypeError("missing required argument: scope") - body = { - "items": items, - "options": options, - "previous_search_id": previous_search_id, - "scope": scope, - } - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_memory_stores_search_memories_request( - name=name, - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreSearchResult, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - def _update_memories_initial( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[List[dict[str, Any]]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any - ) -> Iterator[bytes]: - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) - - if body is _Unset: - if scope is _Unset: - raise TypeError("missing required argument: scope") - body = { - "items": items, - "previous_update_id": previous_update_id, - "scope": scope, - "update_delay": update_delay, - } - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_memory_stores_update_memories_request( - name=name, - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = True - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [202]: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - response_headers = {} - response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) - - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - - return deserialized # type: ignore - - @overload - def _begin_update_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[List[dict[str, Any]]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any - ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: ... - @overload - def _begin_update_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: ... - @overload - def _begin_update_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: ... - - @distributed_trace - def _begin_update_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[List[dict[str, Any]]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any - ) -> LROPoller[_models.MemoryStoreUpdateCompletedResult]: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: Conversation items to be stored in memory. Default value is None. - :paramtype items: list[dict[str, any]] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :return: An instance of LROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.core.polling.LROPoller[~azure.ai.projects.models.MemoryStoreUpdateCompletedResult] - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) - lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) - cont_token: Optional[str] = kwargs.pop("continuation_token", None) - if cont_token is None: - raw_result = self._update_memories_initial( - name=name, - body=body, - foundry_features=_foundry_features, - scope=scope, - items=items, - previous_update_id=previous_update_id, - update_delay=update_delay, - content_type=content_type, - cls=lambda x, y, z: x, - headers=_headers, - params=_params, - **kwargs - ) - raw_result.http_response.read() # type: ignore - kwargs.pop("error_map", None) - - def get_long_running_output(pipeline_response): - response_headers = {} - response = pipeline_response.http_response - response_headers["Operation-Location"] = self._deserialize( - "str", response.headers.get("Operation-Location") - ) - - deserialized = _deserialize(_models.MemoryStoreUpdateCompletedResult, response.json().get("result", {})) - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - return deserialized - - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - - if polling is True: - polling_method: PollingMethod = cast( - PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) - ) - elif polling is False: - polling_method = cast(PollingMethod, NoPolling()) - else: - polling_method = polling - if cont_token: - return LROPoller[_models.MemoryStoreUpdateCompletedResult].from_continuation_token( - polling_method=polling_method, - continuation_token=cont_token, - client=self._client, - deserialization_callback=get_long_running_output, - ) - return LROPoller[_models.MemoryStoreUpdateCompletedResult]( - self._client, raw_result, get_long_running_output, polling_method # type: ignore - ) - - @overload - def delete_scope( - self, name: str, *, scope: str, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. - - :param name: The name of the memory store. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories to delete, such as a - user ID. Required. - :paramtype scope: str - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def delete_scope( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. - - :param name: The name of the memory store. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ +class BetaSchedulesOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. - @overload - def delete_scope( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`schedules` attribute. + """ - :param name: The name of the memory store. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult - :raises ~azure.core.exceptions.HttpResponseError: - """ + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") @distributed_trace - def delete_scope( - self, name: str, body: Union[JSON, IO[bytes]] = _Unset, *, scope: str = _Unset, **kwargs: Any - ) -> _models.MemoryStoreDeleteScopeResult: - """Delete all memories associated with a specific scope from a memory store. + def delete(self, schedule_id: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements + """Delete a schedule. - :param name: The name of the memory store. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories to delete, such as a - user ID. Required. - :paramtype scope: str - :return: MemoryStoreDeleteScopeResult. The MemoryStoreDeleteScopeResult is compatible with - MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :return: None + :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8129,30 +8034,15 @@ def delete_scope( } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreDeleteScopeResult] = kwargs.pop("cls", None) - - if body is _Unset: - if scope is _Unset: - raise TypeError("missing required argument: scope") - body = {"scope": scope} - body = {k: v for k, v in body.items() if v is not None} - content_type = content_type or "application/json" - _content = None - if isinstance(body, (IOBase, bytes)): - _content = body - else: - _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} - _request = build_beta_memory_stores_delete_scope_request( - name=name, + cls: ClsType[None] = kwargs.pop("cls", None) + + _request = build_beta_schedules_delete_request( + schedule_id=schedule_id, foundry_features=_foundry_features, - content_type=content_type, api_version=self._config.api_version, - content=_content, headers=_headers, params=_params, ) @@ -8161,67 +8051,32 @@ def delete_scope( } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) + _stream = False pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [200]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass + if response.status_code not in [204]: map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.MemoryStoreDeleteScopeResult, response.json()) + raise HttpResponseError(response=response) if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - -class BetaRedTeamsOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`red_teams` attribute. - """ - - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + return cls(pipeline_response, None, {}) # type: ignore @distributed_trace - def get(self, name: str, **kwargs: Any) -> _models.RedTeam: - """Get a redteam by name. + def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: + """Get a schedule by id. - :param name: Identifier of the red team run. Required. - :type name: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8234,10 +8089,10 @@ def get(self, name: str, **kwargs: Any) -> _models.RedTeam: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) + cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) - _request = build_beta_red_teams_get_request( - name=name, + _request = build_beta_schedules_get_request( + schedule_id=schedule_id, foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, @@ -8268,7 +8123,7 @@ def get(self, name: str, **kwargs: Any) -> _models.RedTeam: if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.RedTeam, response.json()) + deserialized = _deserialize(_models.Schedule, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -8276,20 +8131,31 @@ def get(self, name: str, **kwargs: Any) -> _models.RedTeam: return deserialized # type: ignore @distributed_trace - def list(self, **kwargs: Any) -> ItemPaged["_models.RedTeam"]: - """List a redteam by name. + def list( + self, + *, + type: Optional[Union[str, _models.ScheduleTaskType]] = None, + enabled: Optional[bool] = None, + **kwargs: Any + ) -> ItemPaged["_models.Schedule"]: + """List all schedules. - :return: An iterator like instance of RedTeam - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.RedTeam] + :keyword type: Filter by the type of schedule. Known values are: "Evaluation" and "Insight". + Default value is None. + :paramtype type: str or ~azure.ai.projects.models.ScheduleTaskType + :keyword enabled: Filter by the enabled status. Default value is None. + :paramtype enabled: bool + :return: An iterator like instance of Schedule + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Schedule] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.RedTeam]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.Schedule]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8302,8 +8168,10 @@ def list(self, **kwargs: Any) -> ItemPaged["_models.RedTeam"]: def prepare_request(next_link=None): if not next_link: - _request = build_beta_red_teams_list_request( + _request = build_beta_schedules_list_request( foundry_features=_foundry_features, + type=type, + enabled=enabled, api_version=self._config.api_version, headers=_headers, params=_params, @@ -8343,7 +8211,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.RedTeam], + List[_models.Schedule], deserialized.get("value", []), ) if cls: @@ -8368,154 +8236,72 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) @overload - def create( - self, red_team: _models.RedTeam, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.RedTeam: - """Creates a redteam run. + def create_or_update( + self, schedule_id: str, schedule: _models.Schedule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. - :param red_team: Redteam to be run. Required. - :type red_team: ~azure.ai.projects.models.RedTeam + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :param schedule: The resource instance. Required. + :type schedule: ~azure.ai.projects.models.Schedule :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create(self, red_team: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.RedTeam: - """Creates a redteam run. + def create_or_update( + self, schedule_id: str, schedule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. - :param red_team: Redteam to be run. Required. - :type red_team: JSON + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :param schedule: The resource instance. Required. + :type schedule: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create(self, red_team: IO[bytes], *, content_type: str = "application/json", **kwargs: Any) -> _models.RedTeam: - """Creates a redteam run. - - :param red_team: Redteam to be run. Required. - :type red_team: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwargs: Any) -> _models.RedTeam: - """Creates a redteam run. - - :param red_team: Redteam to be run. Is one of the following types: RedTeam, JSON, IO[bytes] - Required. - :type red_team: ~azure.ai.projects.models.RedTeam or JSON or IO[bytes] - :return: RedTeam. The RedTeam is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.RedTeam - :raises ~azure.core.exceptions.HttpResponseError: - """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) - error_map: MutableMapping = { - 401: ClientAuthenticationError, - 404: ResourceNotFoundError, - 409: ResourceExistsError, - 304: ResourceNotModifiedError, - } - error_map.update(kwargs.pop("error_map", {}) or {}) - - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.RedTeam] = kwargs.pop("cls", None) - - content_type = content_type or "application/json" - _content = None - if isinstance(red_team, (IOBase, bytes)): - _content = red_team - else: - _content = json.dumps(red_team, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - - _request = build_beta_red_teams_create_request( - foundry_features=_foundry_features, - content_type=content_type, - api_version=self._config.api_version, - content=_content, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - _decompress = kwargs.pop("decompress", True) - _stream = kwargs.pop("stream", False) - pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access - _request, stream=_stream, **kwargs - ) - - response = pipeline_response.http_response - - if response.status_code not in [201]: - if _stream: - try: - response.read() # Load the body in memory and close the socket - except (StreamConsumedError, StreamClosedError): - pass - map_error(status_code=response.status_code, response=response, error_map=error_map) - error = _failsafe_deserialize( - _models.ApiErrorResponse, - response, - ) - raise HttpResponseError(response=response, model=error) - - if _stream: - deserialized = response.iter_bytes() if _decompress else response.iter_raw() - else: - deserialized = _deserialize(_models.RedTeam, response.json()) - - if cls: - return cls(pipeline_response, deserialized, {}) # type: ignore - - return deserialized # type: ignore - - -class BetaSchedulesOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`schedules` attribute. - """ + def create_or_update( + self, schedule_id: str, schedule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. - def __init__(self, *args, **kwargs) -> None: - input_args = list(args) - self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") - self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") - self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") - self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str + :param schedule: The resource instance. Required. + :type schedule: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule + :raises ~azure.core.exceptions.HttpResponseError: + """ @distributed_trace - def delete(self, schedule_id: str, **kwargs: Any) -> None: # pylint: disable=inconsistent-return-statements - """Delete a schedule. + def create_or_update( + self, schedule_id: str, schedule: Union[_models.Schedule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.Schedule: + """Create or update operation template. :param schedule_id: Identifier of the schedule. Required. :type schedule_id: str - :return: None - :rtype: None + :param schedule: The resource instance. Is one of the following types: Schedule, JSON, + IO[bytes] Required. + :type schedule: ~azure.ai.projects.models.Schedule or JSON or IO[bytes] + :return: Schedule. The Schedule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( @@ -8529,15 +8315,25 @@ def delete(self, schedule_id: str, **kwargs: Any) -> None: # pylint: disable=in } error_map.update(kwargs.pop("error_map", {}) or {}) - _headers = kwargs.pop("headers", {}) or {} + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} - cls: ClsType[None] = kwargs.pop("cls", None) + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) - _request = build_beta_schedules_delete_request( + content_type = content_type or "application/json" + _content = None + if isinstance(schedule, (IOBase, bytes)): + _content = schedule + else: + _content = json.dumps(schedule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_schedules_create_or_update_request( schedule_id=schedule_id, foundry_features=_foundry_features, + content_type=content_type, api_version=self._config.api_version, + content=_content, headers=_headers, params=_params, ) @@ -8546,28 +8342,43 @@ def delete(self, schedule_id: str, **kwargs: Any) -> None: # pylint: disable=in } _request.url = self._client.format_url(_request.url, **path_format_arguments) - _stream = False + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access _request, stream=_stream, **kwargs ) response = pipeline_response.http_response - if response.status_code not in [204]: + if response.status_code not in [200, 201]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass map_error(status_code=response.status_code, response=response, error_map=error_map) raise HttpResponseError(response=response) + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Schedule, response.json()) + if cls: - return cls(pipeline_response, None, {}) # type: ignore + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore @distributed_trace - def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: - """Get a schedule by id. + def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.ScheduleRun: + """Get a schedule run by id. - :param schedule_id: Identifier of the schedule. Required. + :param schedule_id: The unique identifier of the schedule. Required. :type schedule_id: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :param run_id: The unique identifier of the schedule run. Required. + :type run_id: str + :return: ScheduleRun. The ScheduleRun is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ScheduleRun :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( @@ -8584,10 +8395,11 @@ def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) + cls: ClsType[_models.ScheduleRun] = kwargs.pop("cls", None) - _request = build_beta_schedules_get_request( + _request = build_beta_schedules_get_run_request( schedule_id=schedule_id, + run_id=run_id, foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, @@ -8613,12 +8425,16 @@ def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Schedule, response.json()) + deserialized = _deserialize(_models.ScheduleRun, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -8626,22 +8442,25 @@ def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: return deserialized # type: ignore @distributed_trace - def list( + def list_runs( self, + schedule_id: str, *, type: Optional[Union[str, _models.ScheduleTaskType]] = None, enabled: Optional[bool] = None, **kwargs: Any - ) -> ItemPaged["_models.Schedule"]: - """List all schedules. + ) -> ItemPaged["_models.ScheduleRun"]: + """List all schedule runs. + :param schedule_id: Identifier of the schedule. Required. + :type schedule_id: str :keyword type: Filter by the type of schedule. Known values are: "Evaluation" and "Insight". Default value is None. :paramtype type: str or ~azure.ai.projects.models.ScheduleTaskType :keyword enabled: Filter by the enabled status. Default value is None. :paramtype enabled: bool - :return: An iterator like instance of Schedule - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Schedule] + :return: An iterator like instance of ScheduleRun + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.ScheduleRun] :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( @@ -8650,7 +8469,7 @@ def list( _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.Schedule]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.ScheduleRun]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8663,7 +8482,8 @@ def list( def prepare_request(next_link=None): if not next_link: - _request = build_beta_schedules_list_request( + _request = build_beta_schedules_list_runs_request( + schedule_id=schedule_id, foundry_features=_foundry_features, type=type, enabled=enabled, @@ -8706,7 +8526,7 @@ def prepare_request(next_link=None): def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.Schedule], + List[_models.ScheduleRun], deserialized.get("value", []), ) if cls: @@ -8730,77 +8550,283 @@ def get_next(next_link=None): return ItemPaged(get_next, extract_data) + +class BetaToolsetsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`toolsets` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: AIProjectClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + @overload - def create_or_update( - self, schedule_id: str, schedule: _models.Schedule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + def create( + self, + *, + name: str, + tools: List[_models.Tool], + content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :keyword name: The name of the toolset. Required. + :paramtype name: str + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def create(self, body: JSON, *, content_type: str = "application/json", **kwargs: Any) -> _models.ToolsetObject: + """Create a toolset. + + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def create( + self, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def create( + self, + body: Union[JSON, IO[bytes]] = _Unset, + *, + name: str = _Unset, + tools: List[_models.Tool] = _Unset, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Create a toolset. + + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword name: The name of the toolset. Required. + :paramtype name: str + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.ToolsetObject] = kwargs.pop("cls", None) + + if body is _Unset: + if name is _Unset: + raise TypeError("missing required argument: name") + if tools is _Unset: + raise TypeError("missing required argument: tools") + body = {"description": description, "metadata": metadata, "name": name, "tools": tools} + body = {k: v for k, v in body.items() if v is not None} + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_beta_toolsets_create_request( + foundry_features=_foundry_features, + content_type=content_type, + api_version=self._config.api_version, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.ToolsetObject, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @overload + def update( + self, + tool_set_name: str, + *, + tools: List[_models.Tool], + content_type: str = "application/json", + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Required. - :type schedule: ~azure.ai.projects.models.Schedule + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_or_update( - self, schedule_id: str, schedule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + def update( + self, tool_set_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Required. - :type schedule: JSON + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :param body: Required. + :type body: JSON :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. Default value is "application/json". :paramtype content_type: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ @overload - def create_or_update( - self, schedule_id: str, schedule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + def update( + self, tool_set_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Required. - :type schedule: IO[bytes] + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :param body: Required. + :type body: IO[bytes] :keyword content_type: Body Parameter content-type. Content type parameter for binary body. Default value is "application/json". :paramtype content_type: str - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ @distributed_trace - def create_or_update( - self, schedule_id: str, schedule: Union[_models.Schedule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.Schedule: - """Create or update operation template. + def update( + self, + tool_set_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + tools: List[_models.Tool] = _Unset, + description: Optional[str] = None, + metadata: Optional[dict[str, str]] = None, + **kwargs: Any + ) -> _models.ToolsetObject: + """Update a toolset. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :param schedule: The resource instance. Is one of the following types: Schedule, JSON, - IO[bytes] Required. - :type schedule: ~azure.ai.projects.models.Schedule or JSON or IO[bytes] - :return: Schedule. The Schedule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Schedule + :param tool_set_name: The name of the toolset to update. Required. + :type tool_set_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword tools: The list of tools to include in the toolset. Required. + :paramtype tools: list[~azure.ai.projects.models.Tool] + :keyword description: A human-readable description of the toolset. Default value is None. + :paramtype description: str + :keyword metadata: Arbitrary key-value metadata to associate with the toolset. Default value is + None. + :paramtype metadata: dict[str, str] + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8814,17 +8840,22 @@ def create_or_update( _params = kwargs.pop("params", {}) or {} content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.Schedule] = kwargs.pop("cls", None) + cls: ClsType[_models.ToolsetObject] = kwargs.pop("cls", None) + if body is _Unset: + if tools is _Unset: + raise TypeError("missing required argument: tools") + body = {"description": description, "metadata": metadata, "tools": tools} + body = {k: v for k, v in body.items() if v is not None} content_type = content_type or "application/json" _content = None - if isinstance(schedule, (IOBase, bytes)): - _content = schedule + if isinstance(body, (IOBase, bytes)): + _content = body else: - _content = json.dumps(schedule, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore - _request = build_beta_schedules_create_or_update_request( - schedule_id=schedule_id, + _request = build_beta_toolsets_update_request( + tool_set_name=tool_set_name, foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, @@ -8845,19 +8876,23 @@ def create_or_update( response = pipeline_response.http_response - if response.status_code not in [200, 201]: + if response.status_code not in [200]: if _stream: try: response.read() # Load the body in memory and close the socket except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.Schedule, response.json()) + deserialized = _deserialize(_models.ToolsetObject, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -8865,19 +8900,17 @@ def create_or_update( return deserialized # type: ignore @distributed_trace - def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.ScheduleRun: - """Get a schedule run by id. + def get(self, tool_set_name: str, **kwargs: Any) -> _models.ToolsetObject: + """Retrieve a toolset. - :param schedule_id: The unique identifier of the schedule. Required. - :type schedule_id: str - :param run_id: The unique identifier of the schedule run. Required. - :type run_id: str - :return: ScheduleRun. The ScheduleRun is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.ScheduleRun + :param tool_set_name: The name of the toolset to retrieve. Required. + :type tool_set_name: str + :return: ToolsetObject. The ToolsetObject is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW ) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8890,11 +8923,10 @@ def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.Sched _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[_models.ScheduleRun] = kwargs.pop("cls", None) + cls: ClsType[_models.ToolsetObject] = kwargs.pop("cls", None) - _request = build_beta_schedules_get_run_request( - schedule_id=schedule_id, - run_id=run_id, + _request = build_beta_toolsets_get_request( + tool_set_name=tool_set_name, foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, @@ -8929,7 +8961,7 @@ def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.Sched if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() else: - deserialized = _deserialize(_models.ScheduleRun, response.json()) + deserialized = _deserialize(_models.ToolsetObject, response.json()) if cls: return cls(pipeline_response, deserialized, {}) # type: ignore @@ -8937,34 +8969,41 @@ def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.Sched return deserialized # type: ignore @distributed_trace - def list_runs( + def list( self, - schedule_id: str, *, - type: Optional[Union[str, _models.ScheduleTaskType]] = None, - enabled: Optional[bool] = None, + limit: Optional[int] = None, + order: Optional[Union[str, _models.PageOrder]] = None, + before: Optional[str] = None, **kwargs: Any - ) -> ItemPaged["_models.ScheduleRun"]: - """List all schedule runs. + ) -> ItemPaged["_models.ToolsetObject"]: + """List all toolsets. - :param schedule_id: Identifier of the schedule. Required. - :type schedule_id: str - :keyword type: Filter by the type of schedule. Known values are: "Evaluation" and "Insight". + :keyword limit: A limit on the number of objects to be returned. Limit can range between 1 and + 100, and the + default is 20. Default value is None. + :paramtype limit: int + :keyword order: Sort order by the ``created_at`` timestamp of the objects. ``asc`` for + ascending order and``desc`` + for descending order. Known values are: "asc" and "desc". Default value is None. + :paramtype order: str or ~azure.ai.projects.models.PageOrder + :keyword before: A cursor for use in pagination. ``before`` is an object ID that defines your + place in the list. + For instance, if you make a list request and receive 100 objects, ending with obj_foo, your + subsequent call can include before=obj_foo in order to fetch the previous page of the list. Default value is None. - :paramtype type: str or ~azure.ai.projects.models.ScheduleTaskType - :keyword enabled: Filter by the enabled status. Default value is None. - :paramtype enabled: bool - :return: An iterator like instance of ScheduleRun - :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.ScheduleRun] + :paramtype before: str + :return: An iterator like instance of ToolsetObject + :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.ToolsetObject] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} - cls: ClsType[List[_models.ScheduleRun]] = kwargs.pop("cls", None) + cls: ClsType[List[_models.ToolsetObject]] = kwargs.pop("cls", None) error_map: MutableMapping = { 401: ClientAuthenticationError, @@ -8974,62 +9013,36 @@ def list_runs( } error_map.update(kwargs.pop("error_map", {}) or {}) - def prepare_request(next_link=None): - if not next_link: - - _request = build_beta_schedules_list_runs_request( - schedule_id=schedule_id, - foundry_features=_foundry_features, - type=type, - enabled=enabled, - api_version=self._config.api_version, - headers=_headers, - params=_params, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) - - else: - # make call to next link with the client's api-version - _parsed_next_link = urllib.parse.urlparse(next_link) - _next_request_params = case_insensitive_dict( - { - key: [urllib.parse.quote(v) for v in value] - for key, value in urllib.parse.parse_qs(_parsed_next_link.query).items() - } - ) - _next_request_params["api-version"] = self._config.api_version - _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, - ) - path_format_arguments = { - "endpoint": self._serialize.url( - "self._config.endpoint", self._config.endpoint, "str", skip_quote=True - ), - } - _request.url = self._client.format_url(_request.url, **path_format_arguments) + def prepare_request(_continuation_token=None): + _request = build_beta_toolsets_list_request( + foundry_features=_foundry_features, + limit=limit, + order=order, + after=_continuation_token, + before=before, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) return _request def extract_data(pipeline_response): deserialized = pipeline_response.http_response.json() list_of_elem = _deserialize( - List[_models.ScheduleRun], - deserialized.get("value", []), + List[_models.ToolsetObject], + deserialized.get("data", []), ) if cls: list_of_elem = cls(list_of_elem) # type: ignore - return deserialized.get("nextLink") or None, iter(list_of_elem) + return deserialized.get("last_id") or None, iter(list_of_elem) - def get_next(next_link=None): - _request = prepare_request(next_link) + def get_next(_continuation_token=None): + _request = prepare_request(_continuation_token) _stream = False pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access @@ -9039,8 +9052,81 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) return pipeline_response return ItemPaged(get_next, extract_data) + + @distributed_trace + def delete(self, tool_set_name: str, **kwargs: Any) -> _models.DeleteToolsetResponse: + """Delete a toolset. + + :param tool_set_name: The name of the toolset to delete. Required. + :type tool_set_name: str + :return: DeleteToolsetResponse. The DeleteToolsetResponse is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.DeleteToolsetResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( + _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW + ) + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.DeleteToolsetResponse] = kwargs.pop("cls", None) + + _request = build_beta_toolsets_delete_request( + tool_set_name=tool_set_name, + foundry_features=_foundry_features, + api_version=self._config.api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.DeleteToolsetResponse, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore diff --git a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd index 028d41916f63..885fc6b2348f 100644 --- a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd +++ b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd @@ -55,4 +55,4 @@ REM Add: REM _SERIALIZER = Serializer() REM _SERIALIZER.client_side_validation = False REM just before the definition of the class BetaOperations (the first class defined in the file) - + diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py index c604386b5cf7..2a15ce981d7b 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search.py @@ -49,9 +49,7 @@ # Upload file to vector store with open(asset_file_path, "rb") as f: - file = openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=f - ) + file = openai_client.vector_stores.files.upload_and_poll(vector_store_id=vector_store.id, file=f) print(f"File uploaded to vector store (id: {file.id})") tool = FileSearchTool(vector_store_ids=[vector_store.id]) diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py index eb17555821be..49501c4a05b7 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream.py @@ -51,9 +51,7 @@ # Upload file to vector store try: with open(asset_file_path, "rb") as f: - file = openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=f - ) + file = openai_client.vector_stores.files.upload_and_poll(vector_store_id=vector_store.id, file=f) print(f"File uploaded to vector store (id: {file.id})") except FileNotFoundError: print(f"Warning: Asset file not found at {asset_file_path}") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py index 0be162b59062..889d8a6b24b8 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_in_stream_async.py @@ -52,9 +52,7 @@ async def main() -> None: # pylint: disable=too-many-statements # Upload file to vector store try: with open(asset_file_path, "rb") as f: - file = await openai_client.vector_stores.files.upload_and_poll( - vector_store_id=vector_store.id, file=f - ) + file = await openai_client.vector_stores.files.upload_and_poll(vector_store_id=vector_store.id, file=f) print(f"File uploaded to vector store (id: {file.id})") except FileNotFoundError: print(f"Warning: Asset file not found at {asset_file_path}") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py index bf0a0120299b..13f695562540 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_synthetic_data_model_evaluation.py @@ -50,7 +50,7 @@ endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] model_deployment_name = os.environ["FOUNDRY_MODEL_NAME"] -with( +with ( DefaultAzureCredential() as credential, AIProjectClient(endpoint=endpoint, credential=credential) as project_client, project_client.get_openai_client() as client, diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py index 4125772e8a42..6d3b54a6dc97 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_workflow_async.py @@ -204,7 +204,7 @@ async def test_async_workflow_non_streaming_with_content_recording( assert AIProjectInstrumentor().is_content_recording_enabled() assert AIProjectInstrumentor().is_instrumented() - project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) + project_client = self.create_async_client(operation_group="tracing", allow_preview=True, **kwargs) deployment_name = kwargs.get("foundry_model_name") assert deployment_name is not None diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 5416b3269e7b..626b9931c279 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -73,7 +73,7 @@ def test_agent_tools_samples(self, sample_path: str, **kwargs) -> None: samples_to_skip=[ "sample_memory_advanced.py", "sample_memory_basic.py", - "sample_memory_crud.py", # Sample works fine. But AI thinks something is wrong. + "sample_memory_crud.py", # Sample works fine. But AI thinks something is wrong. ], ), ) @@ -95,7 +95,9 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: "sample_path", get_sample_paths( "agents", - samples_to_skip=["sample_workflow_multi_agent.py"], # I see in sample spew: "Event 10 type 'response.failed'" with error message in payload "The specified agent was not found. Please verify that the agent name and version are correct". + samples_to_skip=[ + "sample_workflow_multi_agent.py" + ], # I see in sample spew: "Event 10 type 'response.failed'" with error message in payload "The specified agent was not found. Please verify that the agent name and version are correct". ), ) @servicePreparer() diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index 18336f6122a9..14b0f827d0a4 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -170,7 +170,7 @@ class TestSamplesEvaluations(AzureRecordedTestCase): "sample_evaluations_builtin_with_csv.py", # Requires CSV file upload prerequisite "sample_synthetic_data_agent_evaluation.py", # Synthetic data gen is long-running preview feature "sample_synthetic_data_model_evaluation.py", # Synthetic data gen is long-running preview feature - "sample_eval_catalog_prompt_based_evaluators.py", # For some reason fails with 500 (Internal server error) + "sample_eval_catalog_prompt_based_evaluators.py", # For some reason fails with 500 (Internal server error) ], ), ) From 090b9231addb09baba9c8c9a8d5e5bd16f62ebdd Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:08:42 -0700 Subject: [PATCH 14/36] Remove --- sdk/ai/azure-ai-projects/.env.save | 79 ------------------------------ 1 file changed, 79 deletions(-) delete mode 100644 sdk/ai/azure-ai-projects/.env.save diff --git a/sdk/ai/azure-ai-projects/.env.save b/sdk/ai/azure-ai-projects/.env.save deleted file mode 100644 index 6e722704dada..000000000000 --- a/sdk/ai/azure-ai-projects/.env.save +++ /dev/null @@ -1,79 +0,0 @@ -# -# Environment variables that define secrets required for running tests and samples. -# -# All values should be empty by default in this template. -# -# To run tests locally on your device: -# 1. Rename the file to `.env.template` to `.env` -# 2. Fill in the values for the environment variables below (do not commit these changes to the repository!) -# 3. Run the tests (`pytest`) or run samples in the `samples` folder -# - -AZURE_AI_PROJECTS_CONSOLE_LOGGING=true - -####################################################################### -# -# Used in samples -# -# Project endpoint has the format: -# `https://.services.ai.azure.com/api/projects/` - -# Foundry link: https://ai.azure.com/nextgen/r/v9nzDiUtT8iCPPJ9mU_Itw,rg-agentsv2-ga-bugbash,,agentsv2-ga-bugbash-tip,project2/home -#FOUNDRY_PROJECT_ENDPOINT=https://agentsv2-ga-bugbash-tip.services.ai.azure.com/api/projects/project2 -#FOUNDRY_PROJECT_ENDPOINT=https://rovins-sweden.services.ai.azure.com/api/projects/swedenProject -#FOUNDRY_PROJECT_ENDPOINT=https://aoai-jep6bl5hlacma.services.ai.azure.com/api/projects/proj-jep6bl5hlacma -#FOUNDRY_PROJECT_ENDPOINT=https://openai-sdk-aiservices-tip.services.ai.azure.com/api/projects/test-project -FOUNDRY_PROJECT_ENDPOINT=https://balapv-1dp-prod-account1.services.ai.azure.com/api/projects/project1 -#FOUNDRY_PROJECT_ENDPOINT=https://aoai-3vdvhhtwxyahm.services.ai.azure.com/api/projects/proj-3vdvhhtwxyahm -#FOUNDRY_MODEL_NAME=gpt-4.1-mini -FOUNDRY_MODEL_NAME=gpt-4o-mini -FOUNDRY_AGENT_NAME=My-Agent -CONVERSATION_ID= -CONNECTION_NAME= -AZURE_AI_PROJECTS_AZURE_SUBSCRIPTION_ID= -AZURE_AI_PROJECTS_AZURE_RESOURCE_GROUP= -AZURE_AI_PROJECTS_AZURE_AOAI_ACCOUNT= -FABRIC_USER_INPUT= -A2A_USER_INPUT= -BING_CUSTOM_USER_INPUT= - -# Used in Memory Store samples -#MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME=gpt-4.1-mini -MEMORY_STORE_CHAT_MODEL_DEPLOYMENT_NAME=gpt-4o-mini -MEMORY_STORE_EMBEDDING_MODEL_DEPLOYMENT_NAME=text-embedding-3-large - -# Used in Agent tools samples -IMAGE_GENERATION_MODEL_DEPLOYMENT_NAME=gpt-image-1 -BING_PROJECT_CONNECTION_ID= -MCP_PROJECT_CONNECTION_ID= -FABRIC_PROJECT_CONNECTION_ID= -AI_SEARCH_PROJECT_CONNECTION_ID= -AI_SEARCH_INDEX_NAME= -BING_CUSTOM_SEARCH_PROJECT_CONNECTION_ID= -BING_CUSTOM_SEARCH_INSTANCE_NAME= -SHAREPOINT_PROJECT_CONNECTION_ID= -A2A_PROJECT_CONNECTION_ID= -BROWSER_AUTOMATION_PROJECT_CONNECTION_ID= -OPENAPI_PROJECT_CONNECTION_ID= -AI_SEARCH_USER_INPUT= -SHAREPOINT_USER_INPUT= - - -####################################################################### -# -# Used in tests -####################################################################### - -# Used for recording or playback -AZURE_TEST_RUN_LIVE=true -AZURE_SKIP_LIVE_RECORDING=true - -# Used in Fine-tuning tests -COMPLETED_OAI_MODEL_SFT_FINE_TUNING_JOB_ID= -COMPLETED_OAI_MODEL_RFT_FINE_TUNING_JOB_ID= -COMPLETED_OAI_MODEL_DPO_FINE_TUNING_JOB_ID= -COMPLETED_OSS_MODEL_SFT_FINE_TUNING_JOB_ID= -RUNNING_FINE_TUNING_JOB_ID= -PAUSED_FINE_TUNING_JOB_ID= -AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP= \ No newline at end of file From d27b152054483ee533e773e8ce1c4358e0aeea67 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:27:14 -0700 Subject: [PATCH 15/36] Update project status to Production/Stable --- sdk/ai/azure-ai-projects/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/pyproject.toml b/sdk/ai/azure-ai-projects/pyproject.toml index e2ae27e26534..f5babde606a2 100644 --- a/sdk/ai/azure-ai-projects/pyproject.toml +++ b/sdk/ai/azure-ai-projects/pyproject.toml @@ -17,7 +17,7 @@ authors = [ description = "Microsoft Corporation Azure AI Projects Client Library for Python" license = "MIT" classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", From b70d34967c8aa1071c5b2b3f97aa7b68b47a6bdb Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:14:57 -0700 Subject: [PATCH 16/36] Classes `UpdateMemoriesLROPollingMethod` and `AsyncUpdateMemoriesLROPollingMethod` should be private (#45685) --- .../aio/operations/_patch_memories_async.py | 16 +++++++++------- .../azure/ai/projects/models/_patch.py | 10 ++++------ .../ai/projects/operations/_patch_memories.py | 16 +++++++++------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py index 31c1b98eea09..86fa98998195 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py @@ -20,8 +20,8 @@ ResponseUsageOutputTokensDetails, MemoryStoreUpdateCompletedResult, AsyncUpdateMemoriesLROPoller, - AsyncUpdateMemoriesLROPollingMethod, ) +from ...models._patch import _AsyncUpdateMemoriesLROPollingMethod from ...models._enums import _FoundryFeaturesOptInKeys from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations from ...operations._patch_memories import _serialize_memory_input_items @@ -296,7 +296,9 @@ async def begin_update_memories( content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling: Union[bool, AsyncUpdateMemoriesLROPollingMethod] = kwargs.pop("polling", True) + polling = kwargs.pop("polling", True) + if not isinstance(polling, bool): + raise TypeError("polling must be of type bool.") lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) cont_token: Optional[str] = kwargs.pop("continuation_token", None) if cont_token is None: @@ -348,17 +350,16 @@ def get_long_running_output(pipeline_response): "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), } - if polling is True: - polling_method: AsyncUpdateMemoriesLROPollingMethod = AsyncUpdateMemoriesLROPollingMethod( + if polling: + polling_method: _AsyncUpdateMemoriesLROPollingMethod = _AsyncUpdateMemoriesLROPollingMethod( lro_delay, path_format_arguments=path_format_arguments, headers={"Foundry-Features": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value}, **kwargs, ) - elif polling is False: - polling_method = cast(AsyncUpdateMemoriesLROPollingMethod, AsyncNoPolling()) else: - polling_method = polling + polling_method = cast(_AsyncUpdateMemoriesLROPollingMethod, AsyncNoPolling()) + if cont_token: return AsyncUpdateMemoriesLROPoller.from_continuation_token( polling_method=polling_method, @@ -366,6 +367,7 @@ def get_long_running_output(pipeline_response): client=self._client, deserialization_callback=get_long_running_output, ) + return AsyncUpdateMemoriesLROPoller( self._client, raw_result, # type: ignore[possibly-undefined] diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index eb69fd4e6836..ed7f7e804883 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -68,7 +68,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: _FAILED = frozenset(["failed"]) -class UpdateMemoriesLROPollingMethod(LROBasePolling): +class _UpdateMemoriesLROPollingMethod(LROBasePolling): """A custom polling method implementation for Memory Store updates.""" @property @@ -139,7 +139,7 @@ def _poll(self) -> None: _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) -class AsyncUpdateMemoriesLROPollingMethod(AsyncLROBasePolling): +class _AsyncUpdateMemoriesLROPollingMethod(AsyncLROBasePolling): """A custom polling method implementation for Memory Store updates.""" @property @@ -213,7 +213,7 @@ async def _poll(self) -> None: class UpdateMemoriesLROPoller(LROPoller[MemoryStoreUpdateCompletedResult]): """Custom LROPoller for Memory Store update operations.""" - _polling_method: "UpdateMemoriesLROPollingMethod" + _polling_method: "_UpdateMemoriesLROPollingMethod" @property def update_id(self) -> str: @@ -263,7 +263,7 @@ def from_continuation_token( class AsyncUpdateMemoriesLROPoller(AsyncLROPoller[MemoryStoreUpdateCompletedResult]): """Custom AsyncLROPoller for Memory Store update operations.""" - _polling_method: "AsyncUpdateMemoriesLROPollingMethod" + _polling_method: "_AsyncUpdateMemoriesLROPollingMethod" @property def update_id(self) -> str: @@ -315,8 +315,6 @@ def from_continuation_token( __all__: List[str] = [ "CustomCredential", - "UpdateMemoriesLROPollingMethod", - "AsyncUpdateMemoriesLROPollingMethod", "UpdateMemoriesLROPoller", "AsyncUpdateMemoriesLROPoller", ] # Add all objects you want publicly available to users at this package level diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py index 100919e7e40a..a946cfd11080 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py @@ -20,8 +20,8 @@ ResponseUsageOutputTokensDetails, MemoryStoreUpdateCompletedResult, UpdateMemoriesLROPoller, - UpdateMemoriesLROPollingMethod, ) +from ..models._patch import _UpdateMemoriesLROPollingMethod from ..models._enums import _FoundryFeaturesOptInKeys from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations from .._validation import api_version_validation @@ -331,7 +331,9 @@ def begin_update_memories( content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) cls: ClsType[MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling: Union[bool, UpdateMemoriesLROPollingMethod] = kwargs.pop("polling", True) + polling = kwargs.pop("polling", True) + if not isinstance(polling, bool): + raise TypeError("polling must be of type bool.") lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) cont_token: Optional[str] = kwargs.pop("continuation_token", None) if cont_token is None: @@ -383,17 +385,16 @@ def get_long_running_output(pipeline_response): "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), } - if polling is True: - polling_method: UpdateMemoriesLROPollingMethod = UpdateMemoriesLROPollingMethod( + if polling: + polling_method: _UpdateMemoriesLROPollingMethod = _UpdateMemoriesLROPollingMethod( lro_delay, path_format_arguments=path_format_arguments, headers={"Foundry-Features": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value}, **kwargs, ) - elif polling is False: - polling_method = cast(UpdateMemoriesLROPollingMethod, NoPolling()) else: - polling_method = polling + polling_method = cast(_UpdateMemoriesLROPollingMethod, NoPolling()) + if cont_token: return UpdateMemoriesLROPoller.from_continuation_token( polling_method=polling_method, @@ -401,6 +402,7 @@ def get_long_running_output(pipeline_response): client=self._client, deserialization_callback=get_long_running_output, ) + return UpdateMemoriesLROPoller( self._client, raw_result, # type: ignore[possibly-undefined] From cb6748fb1e326a665ca3302929cf5ddc897ca16b Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Mon, 16 Mar 2026 11:37:36 -0700 Subject: [PATCH 17/36] Unit-tests to make sure "Foundry-Features" HTTP request header is added when relevant (#45718) --- .../azure/ai/projects/aio/_patch.py | 1 - .../foundry_features_header_test_base.py | 197 +++++++++++++++ .../test_foundry_features_header.py | 212 ++++++++++++++++ .../test_foundry_features_header_async.py | 232 ++++++++++++++++++ .../test_foundry_features_header_optional.py | 164 +++++++++++++ ..._foundry_features_header_optional_async.py | 174 +++++++++++++ 6 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py 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 837ca0b1942f..4f8312c6996e 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 @@ -66,7 +66,6 @@ def __init__( self, endpoint: str, credential: AsyncTokenCredential, *, allow_preview: bool = False, **kwargs: Any ) -> None: - self._allow_preview = allow_preview self._console_logging_enabled: bool = ( os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" ) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py new file mode 100644 index 000000000000..22cf12076aa6 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py @@ -0,0 +1,197 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +""" +Shared base class and helpers for Foundry-Features HTTP header tests (sync and async). + +The following are shared by both test_required_header.py (sync) and +test_required_header_async.py (async): + + - Constants: FAKE_ENDPOINT, FOUNDRY_FEATURES_HEADER, EXPECTED_FOUNDRY_FEATURES + - _RequestCaptured exception sentinel + - FakeCredential / AsyncFakeCredential stubs + - FoundryFeaturesHeaderTestBase with utility/assertion class methods +""" + +import inspect +from typing import Any, ClassVar, List, Tuple, Union, get_origin + +from azure.core.credentials import AccessToken + +# Both sentinel values used to mark "required but unset" parameters. +# The sync and async generated _operations modules define _Unset independently. +from azure.ai.projects.operations._operations import _Unset as _SyncUnset +from azure.ai.projects.aio.operations._operations import _Unset as _AsyncUnset + +FAKE_ENDPOINT = "https://fake-account.services.ai.azure.com/api/projects/fake-project" +FOUNDRY_FEATURES_HEADER = "Foundry-Features" + +# Expected Foundry-Features header value for each .beta sub-client. +# If a new sub-client is added to .beta and is missing from this mapping, the test will +# fail at collection time with a message asking you to add it here. +EXPECTED_FOUNDRY_FEATURES: dict[str, str] = { + "evaluation_taxonomies": "Evaluations=V1Preview", + "evaluators": "Evaluations=V1Preview", + "insights": "Insights=V1Preview", + "memory_stores": "MemoryStores=V1Preview", + "red_teams": "RedTeams=V1Preview", + "schedules": "Schedules=V1Preview", + "toolsets": "Toolsets=V1Preview", +} + +# Both sentinel values – used by _make_fake_call to detect required parameters +# whose defaults are the internal _Unset object (rather than inspect.Parameter.empty). +_UNSET_SENTINELS: frozenset = frozenset({_SyncUnset, _AsyncUnset}) + + +# --------------------------------------------------------------------------- +# Sentinel exception raised by capturing transports +# --------------------------------------------------------------------------- + + +class _RequestCaptured(Exception): + """Raised by CapturingTransport / CapturingAsyncTransport to abort I/O early.""" + + def __init__(self, request: Any) -> None: + self.request = request + super().__init__("request captured") + + +# --------------------------------------------------------------------------- +# Stub credentials +# --------------------------------------------------------------------------- + + +class FakeCredential: + """Sync stub credential that returns a never-expiring token.""" + + def get_token(self, *args: Any, **kwargs: Any) -> AccessToken: + return AccessToken("fake-token", 9_999_999_999) + + +class AsyncFakeCredential: + """Async stub credential that returns a never-expiring token.""" + + async def get_token(self, *args: Any, **kwargs: Any) -> AccessToken: + return AccessToken("fake-token", 9_999_999_999) + + +# --------------------------------------------------------------------------- +# Base test class +# --------------------------------------------------------------------------- + + +class FoundryFeaturesHeaderTestBase: + """Base class with utilities shared by sync and async Foundry-Features header tests. + + Subclasses must define their own *class-level* ``_report``, ``_report_absent``, + ``_report_max_label_len``, and ``_report_absent_max_label_len`` so that each test + module accumulates its own reports and they do not bleed into one another. + """ + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + _report_absent: ClassVar[List[Tuple[str, str]]] = [] + _report_absent_max_label_len: ClassVar[int] = 0 + + # ------------------------------------------------------------------ + # Fake-argument builder + # ------------------------------------------------------------------ + + @staticmethod + def _fake_for_param(param: inspect.Parameter) -> Any: + """Return a plausible fake value for a single required parameter. + + Resolution order: + - Union types (e.g. Union[Model, JSON, IO[bytes]]) -> {} (JSON body) + - list / List[...] -> [] + - str -> "fake-value" + - int -> 0 + - bool -> False + - anything else (model classes, etc.) -> {} + """ + ann = param.annotation + if ann is inspect.Parameter.empty: + return "fake-value" + + origin = get_origin(ann) + if origin is Union: + return {} + if origin is list: + return [] + if ann is str: + return "fake-value" + if ann is int: + return 0 + if ann is bool: + return False + return {} + + @classmethod + def _make_fake_call(cls, method: Any) -> Any: + """Return a zero-argument callable that invokes *method* with fake args. + + Only required parameters (no default, or default is the _Unset sentinel + from either the sync or async generated operations module) are populated. + Optional ones are omitted so as not to trigger extra validation paths. + """ + sig = inspect.signature(method) + args: list[Any] = [] + kwargs: dict[str, Any] = {} + + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD): + continue + + is_required = ( + param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS + ) + if not is_required: + continue + + fake = cls._fake_for_param(param) + if param.kind in (param.POSITIONAL_OR_KEYWORD, param.POSITIONAL_ONLY): + args.append(fake) + else: # KEYWORD_ONLY + kwargs[param_name] = fake + + return lambda: method(*args, **kwargs) + + # ------------------------------------------------------------------ + # Assertion + report recording (shared logic, transport-agnostic) + # ------------------------------------------------------------------ + + @classmethod + def _record_header_assertion(cls, label: str, request: Any, expected_value: str) -> str: + """Assert the Foundry-Features header on *request* equals *expected_value*, + then record the result in *cls._report* for the end-of-session summary. + """ + header_value: str | None = request.headers.get(FOUNDRY_FEATURES_HEADER) + assert header_value, ( + f"{label}: REST call was made but '{FOUNDRY_FEATURES_HEADER}' header is " + f"missing or empty.\nActual headers: {dict(request.headers)}" + ) + assert header_value == expected_value, ( + f"{label}: expected '{FOUNDRY_FEATURES_HEADER}: {expected_value}' " + f"but got '{header_value}'" + ) + cls._report_max_label_len = max(cls._report_max_label_len, len(label)) + cls._report.append((label, header_value)) + return header_value + + @classmethod + def _record_header_absence_assertion(cls, label: str, request: Any) -> None: + """Assert the Foundry-Features header is absent on *request*, + then record the result in *cls._report_absent* for the end-of-module summary. + """ + assert FOUNDRY_FEATURES_HEADER not in request.headers, ( + f"{label}: expected '{FOUNDRY_FEATURES_HEADER}' header to be absent.\n" + f"Actual headers: {dict(request.headers)}" + ) + absence_note = f'\'{FOUNDRY_FEATURES_HEADER}\' header not present (as expected)' + cls._report_absent_max_label_len = max(cls._report_absent_max_label_len, len(label)) + cls._report_absent.append((label, absence_note)) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py new file mode 100644 index 000000000000..71fdf4fd4212 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py @@ -0,0 +1,212 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +""" +Test that every public method on AIProjectClient.beta sub-clients sends the +'Foundry-Features' HTTP request header. + +This test does NOT make real network calls. It uses a custom transport that +captures the outgoing HttpRequest and raises a sentinel exception, so no +actual HTTP traffic occurs. + +Sub-clients and their methods are discovered dynamically at collection time via +introspection, so the test automatically covers new methods added to .beta and +stops covering methods that are removed — no manual updates needed. + +Discovery strategy: + - Non-callable public attributes of `client.beta` are sub-clients + (e.g. evaluation_taxonomies, memory_stores, …). + - Public bound methods on each sub-client are the API methods to test. + - Fake arguments are inferred from each method's signature: required + parameters (those with no default, or whose default is the _Unset sentinel) + receive a type-appropriate placeholder value. + +Run with: pytest tests/foundry_features_header/test_required_header.py -s +The -s flag (or --capture=no) is required to see the printed report. + +NOTE: This does not test follow up paging calls for "list" operations. It only +tests the first call. +""" + +import inspect +from typing import Any, ClassVar, Iterator, List, Tuple + +import pytest +from azure.core.pipeline.transport import HttpTransport +from azure.ai.projects import AIProjectClient + +from foundry_features_header_test_base import ( + EXPECTED_FOUNDRY_FEATURES, + FAKE_ENDPOINT, + FakeCredential, + FoundryFeaturesHeaderTestBase, + _RequestCaptured, +) + + +# --------------------------------------------------------------------------- +# Sync-specific transport +# --------------------------------------------------------------------------- + + +class CapturingTransport(HttpTransport): + """Sync transport that captures the outgoing request and raises _RequestCaptured.""" + + def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] + raise _RequestCaptured(request) + + def open(self) -> None: + pass + + def close(self) -> None: + pass + + def __enter__(self) -> "CapturingTransport": + return self + + def __exit__(self, *args: Any) -> None: + pass + + +# --------------------------------------------------------------------------- +# Dynamic test-case discovery (runs at collection time, not at test time) +# --------------------------------------------------------------------------- + + +def _discover_test_cases() -> list[pytest.param]: + """Introspect AIProjectClient.beta and yield one pytest.param per public method. + + Steps: + 1. Instantiate a temporary client (no network calls are made here). + 2. Non-callable public attributes of client.beta are sub-clients. + 3. Public bound methods on each sub-client are the API methods to test. + """ + temp = AIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=FakeCredential(), # type: ignore[arg-type] + transport=CapturingTransport(), + ) + + cases: list[pytest.param] = [] + for sc_name in sorted(dir(temp.beta)): + if sc_name.startswith("_"): + continue + sc = getattr(temp.beta, sc_name) + # Sub-clients are non-callable objects (instances of operations classes). + # Skip anything callable (e.g. methods directly on BetaOperations itself). + if callable(sc): + continue + + assert sc_name in EXPECTED_FOUNDRY_FEATURES, ( + f"New .beta sub-client '{sc_name}' discovered but not found in EXPECTED_FOUNDRY_FEATURES. " + f"Please add an entry for '.beta.{sc_name}' to the EXPECTED_FOUNDRY_FEATURES mapping in " + f"base_test.py." + ) + + for m_name in sorted(dir(sc)): + if m_name.startswith("_"): + continue + method = getattr(sc, m_name) + if not inspect.ismethod(method): + continue + + label = f".beta.{sc_name}.{m_name}()" + expected = EXPECTED_FOUNDRY_FEATURES[sc_name] + cases.append(pytest.param(label, sc_name, m_name, expected, id=label)) + + return cases + + +_TEST_CASES = _discover_test_cases() + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(scope="module") +def client() -> Iterator[AIProjectClient]: + with AIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=FakeCredential(), # type: ignore[arg-type] + transport=CapturingTransport(), + ) as c: + yield c + + +@pytest.fixture(scope="module", autouse=True) +def _print_report() -> Iterator[None]: + """Print the full Foundry-Features header report after all tests finish.""" + yield + report = TestFoundryFeaturesHeader._report + max_len = TestFoundryFeaturesHeader._report_max_label_len + if report: + print("\n\nFoundry-Features header report (sync):") + for label, header_value in sorted(report): + print(f"{label:<{max_len}} | \"{header_value}\"") + + +# --------------------------------------------------------------------------- +# Test class +# --------------------------------------------------------------------------- + + +class TestFoundryFeaturesHeader(FoundryFeaturesHeaderTestBase): + """Sync tests: assert every public .beta method sends the Foundry-Features header.""" + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + + # ------------------------------------------------------------------ + # Sync capture + # ------------------------------------------------------------------ + + @staticmethod + def _capture(call: Any) -> Any: + """Call *call()* and return the captured HttpRequest. + + Most methods raise _RequestCaptured immediately (transport is hit + synchronously). Lazy methods that return an ItemPaged only call the + transport when the first item is fetched, so we trigger that with next(). + """ + try: + result = call() + except _RequestCaptured as exc: + return exc.request + + # call() returned without raising -> lazy iterable (ItemPaged) + try: + next(iter(result)) + except _RequestCaptured as exc: + return exc.request + except StopIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + + raise AssertionError("Transport was never called") + + @classmethod + def _assert_header(cls, label: str, call: Any, expected_value: str) -> str: + """Invoke *call*, capture the request, and assert the header is present and correct.""" + request = cls._capture(call) + return cls._record_header_assertion(label, request, expected_value) + + # ------------------------------------------------------------------ + # Parametrized test + # ------------------------------------------------------------------ + + @pytest.mark.parametrize("label,subclient_name,method_name,expected_header_value", _TEST_CASES) + def test_foundry_features_header( + self, + client: AIProjectClient, + label: str, + subclient_name: str, + method_name: str, + expected_header_value: str, + ) -> None: + """Assert that *method_name* on .beta. sends the expected Foundry-Features value.""" + sc = getattr(client.beta, subclient_name) + method = getattr(sc, method_name) + self._assert_header(label, self._make_fake_call(method), expected_header_value) \ No newline at end of file diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py new file mode 100644 index 000000000000..73db8c64491d --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py @@ -0,0 +1,232 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +""" +Test that every public async method on AIProjectClient.beta sub-clients sends the +'Foundry-Features' HTTP request header. + +This test does NOT make real network calls. It uses a custom async transport that +captures the outgoing HttpRequest and raises a sentinel exception, so no +actual HTTP traffic occurs. + +Sub-clients and their methods are discovered dynamically at collection time via +introspection, so the test automatically covers new methods added to .beta and +stops covering methods that are removed — no manual updates needed. + +Discovery strategy: + - Non-callable public attributes of async `client.beta` are sub-clients + (e.g. evaluation_taxonomies, memory_stores, …). + - Public bound methods on each sub-client are the API methods to test. + Both ``async def`` coroutine methods and regular ``def`` methods that + return an ``AsyncItemPaged`` are discovered. + - Fake arguments are inferred from each method's signature: required + parameters (those with no default, or whose default is the _Unset sentinel) + receive a type-appropriate placeholder value. + +Run with: pytest tests/foundry_features_header/test_required_header_async.py -s +The -s flag (or --capture=no) is required to see the printed report. + +NOTE: This does not test follow up paging calls for "list" operations. It only +tests the first call. +""" + +import inspect +from typing import Any, ClassVar, Iterator, List, Tuple + +import pytest +from azure.core.pipeline.transport import AsyncHttpTransport +from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient + +from foundry_features_header_test_base import ( + EXPECTED_FOUNDRY_FEATURES, + FAKE_ENDPOINT, + AsyncFakeCredential, + FoundryFeaturesHeaderTestBase, + _RequestCaptured, +) + + +# --------------------------------------------------------------------------- +# Async-specific transport +# --------------------------------------------------------------------------- + + +class CapturingAsyncTransport(AsyncHttpTransport): + """Async transport that captures the outgoing request and raises _RequestCaptured.""" + + async def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] + raise _RequestCaptured(request) + + async def open(self) -> None: + pass + + async def close(self) -> None: + pass + + async def __aenter__(self) -> "CapturingAsyncTransport": + return self + + async def __aexit__(self, *args: Any) -> None: + pass + + +# --------------------------------------------------------------------------- +# Dynamic test-case discovery (runs at collection time, not at test time) +# --------------------------------------------------------------------------- + + +def _discover_async_test_cases() -> list[pytest.param]: + """Introspect async AIProjectClient.beta and yield one pytest.param per public method. + + Steps: + 1. Instantiate a temporary async client (no network calls are made here; + the constructor is synchronous). + 2. Non-callable public attributes of client.beta are sub-clients. + 3. Public bound methods on each sub-client are the API methods to test. + Both coroutine methods (async def) and regular methods returning + AsyncItemPaged satisfy inspect.ismethod() and are both included. + """ + temp = AsyncAIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=AsyncFakeCredential(), # type: ignore[arg-type] + transport=CapturingAsyncTransport(), + ) + + cases: list[pytest.param] = [] + for sc_name in sorted(dir(temp.beta)): + if sc_name.startswith("_"): + continue + sc = getattr(temp.beta, sc_name) + # Sub-clients are non-callable objects (instances of operations classes). + # Skip anything callable (e.g. methods directly on BetaOperations itself). + if callable(sc): + continue + + assert sc_name in EXPECTED_FOUNDRY_FEATURES, ( + f"New .beta sub-client '{sc_name}' discovered but not found in EXPECTED_FOUNDRY_FEATURES. " + f"Please add an entry for '.beta.{sc_name}' to the EXPECTED_FOUNDRY_FEATURES mapping in " + f"base_test.py." + ) + + for m_name in sorted(dir(sc)): + if m_name.startswith("_"): + continue + method = getattr(sc, m_name) + if not inspect.ismethod(method): + continue + + label = f".beta.{sc_name}.{m_name}() [async]" + expected = EXPECTED_FOUNDRY_FEATURES[sc_name] + cases.append(pytest.param(label, sc_name, m_name, expected, id=label)) + + return cases + + +_ASYNC_TEST_CASES = _discover_async_test_cases() + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + + +@pytest.fixture(scope="module") +def async_client() -> Iterator[AsyncAIProjectClient]: + """Provide a module-scoped async client backed by the capturing transport. + + The AIProjectClient constructor is synchronous, so no async fixture is + needed. Because the transport never performs real I/O, there is nothing + to clean up after the tests. + """ + yield AsyncAIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=AsyncFakeCredential(), # type: ignore[arg-type] + transport=CapturingAsyncTransport(), + ) + + +@pytest.fixture(scope="module", autouse=True) +def _print_report_async() -> Iterator[None]: + """Print the full Foundry-Features header report after all async tests finish.""" + yield + report = TestFoundryFeaturesHeaderAsync._report + max_len = TestFoundryFeaturesHeaderAsync._report_max_label_len + if report: + print("\n\nFoundry-Features header report (async):") + for label, header_value in sorted(report): + print(f"{label:<{max_len}} | \"{header_value}\"") + + +# --------------------------------------------------------------------------- +# Test class +# --------------------------------------------------------------------------- + + +class TestFoundryFeaturesHeaderAsync(FoundryFeaturesHeaderTestBase): + """Async tests: assert every public async .beta method sends the Foundry-Features header.""" + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + + # ------------------------------------------------------------------ + # Async capture + # ------------------------------------------------------------------ + + @staticmethod + async def _capture_async(call: Any) -> Any: + """Invoke *call()* and return the captured HttpRequest. + + Two cases are handled: + 1. ``async def`` methods: calling them returns an awaitable (coroutine). + Awaiting it will hit the transport and raise _RequestCaptured. + 2. Regular ``def`` methods returning ``AsyncItemPaged``: calling them + returns an async iterable. Advancing to the first item triggers + the transport and raises _RequestCaptured. + """ + result = call() + + if inspect.isawaitable(result): + # Coroutine path (async def method) + try: + await result + except _RequestCaptured as exc: + return exc.request + raise AssertionError("Transport was never called (awaitable completed without raising)") + + # AsyncItemPaged path — advance to the first page to trigger the transport. + ai = result.__aiter__() + try: + await ai.__anext__() + except _RequestCaptured as exc: + return exc.request + except StopAsyncIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + + raise AssertionError("Transport was never called") + + @classmethod + async def _assert_header_async(cls, label: str, call: Any, expected_value: str) -> str: + """Invoke *call*, capture the request, and assert the header is present and correct.""" + request = await cls._capture_async(call) + return cls._record_header_assertion(label, request, expected_value) + + # ------------------------------------------------------------------ + # Parametrized test + # ------------------------------------------------------------------ + + @pytest.mark.asyncio + @pytest.mark.parametrize("label,subclient_name,method_name,expected_header_value", _ASYNC_TEST_CASES) + async def test_foundry_features_header_async( + self, + async_client: AsyncAIProjectClient, + label: str, + subclient_name: str, + method_name: str, + expected_header_value: str, + ) -> None: + """Assert that *method_name* on async .beta. sends the expected Foundry-Features value.""" + sc = getattr(async_client.beta, subclient_name) + method = getattr(sc, method_name) + await self._assert_header_async(label, self._make_fake_call(method), expected_header_value) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py new file mode 100644 index 000000000000..ef32e04421f7 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py @@ -0,0 +1,164 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Tests optional Foundry-Features header behavior on non-beta sync methods. + +These tests hard-code the non-beta methods that optionally send the +Foundry-Features header and verify both modes: + - allow_preview=True -> header is present with expected value + - allow_preview unset -> header is absent +""" + +from typing import Any, ClassVar, Iterator, List, Tuple + +import pytest +from azure.core.pipeline.transport import HttpTransport +from azure.ai.projects import AIProjectClient + +from foundry_features_header_test_base import ( + FAKE_ENDPOINT, + FakeCredential, + FoundryFeaturesHeaderTestBase, + _RequestCaptured, +) + + +_NON_BETA_OPTIONAL_TEST_CASES = [ + # Each pytest.param entry has the following positional arguments: + # 1. method_name (str) – "." on AIProjectClient, e.g. "agents.create_version" + # The subclient and method names are parsed automatically from this string. + # 2. expected_header_value (str) – Expected value of the Foundry-Features header when allow_preview=True. + # Use a comma-separated list of feature=version pairs, e.g. "FeatureA=V1Preview,FeatureB=V1Preview". + # The test id is derived automatically from method_name. + pytest.param( + "agents.create_version", + "HostedAgents=V1Preview,WorkflowAgents=V1Preview", + ), + pytest.param( + "evaluation_rules.create_or_update", + "Evaluations=V1Preview", + ), +] + + +class CapturingTransport(HttpTransport): + """Sync transport that captures the outgoing request and raises _RequestCaptured.""" + + def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] + raise _RequestCaptured(request) + + def open(self) -> None: + pass + + def close(self) -> None: + pass + + def __enter__(self) -> "CapturingTransport": + return self + + def __exit__(self, *args: Any) -> None: + pass + + +@pytest.fixture(scope="module") +def client_preview_enabled() -> Iterator[AIProjectClient]: + with AIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=FakeCredential(), # type: ignore[arg-type] + allow_preview=True, + transport=CapturingTransport(), + ) as c: + yield c + + +@pytest.fixture(scope="module") +def client_preview_disabled() -> Iterator[AIProjectClient]: + with AIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=FakeCredential(), # type: ignore[arg-type] + transport=CapturingTransport(), + ) as c: + yield c + + +@pytest.fixture(scope="module", autouse=True) +def _print_report_optional() -> Iterator[None]: + """Print two Foundry-Features reports after all sync optional tests finish: + one for the allow_preview=True test and one for the allow_preview-unset test. + """ + yield + present_report = TestFoundryFeaturesHeaderOptional._report + if present_report: + max_len = TestFoundryFeaturesHeaderOptional._report_max_label_len + print("\n\nFoundry-Features optional header report (sync) — test_optional_header_present_when_preview_enabled:") + for label, header_value in sorted(present_report): + print(f"{label:<{max_len}} | \"{header_value}\"") + + absent_report = TestFoundryFeaturesHeaderOptional._report_absent + if absent_report: + max_len = TestFoundryFeaturesHeaderOptional._report_absent_max_label_len + print("\n\nFoundry-Features optional header report (sync) — test_optional_header_absent_when_preview_not_enabled:") + for label, header_value in sorted(absent_report): + print(f"{label:<{max_len}} | \"{header_value}\"") + + +class TestFoundryFeaturesHeaderOptional(FoundryFeaturesHeaderTestBase): + """Sync tests for optional Foundry-Features header behavior on non-beta methods.""" + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + _report_absent: ClassVar[List[Tuple[str, str]]] = [] + _report_absent_max_label_len: ClassVar[int] = 0 + + @staticmethod + def _capture(call: Any) -> Any: + """Call *call()* and return the captured HttpRequest.""" + try: + result = call() + except _RequestCaptured as exc: + return exc.request + + try: + next(iter(result)) + except _RequestCaptured as exc: + return exc.request + except StopIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + + raise AssertionError("Transport was never called") + + @classmethod + def _assert_header_present(cls, label: str, call: Any, expected_value: str) -> None: + request = cls._capture(call) + cls._record_header_assertion(label, request, expected_value) + + @classmethod + def _assert_header_absent(cls, label: str, call: Any) -> None: + request = cls._capture(call) + cls._record_header_absence_assertion(label, request) + + @pytest.mark.parametrize("method_name,expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_optional_header_present_when_preview_enabled( + self, + client_preview_enabled: AIProjectClient, + method_name: str, + expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + self._assert_header_present(method_name, self._make_fake_call(method), expected_header_value) + + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_optional_header_absent_when_preview_not_enabled( + self, + client_preview_disabled: AIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_disabled, subclient_name) + method = getattr(sc, method_attr) + self._assert_header_absent(method_name, self._make_fake_call(method)) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py new file mode 100644 index 000000000000..06e2c21637f0 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py @@ -0,0 +1,174 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Tests optional Foundry-Features header behavior on non-beta async methods. + +These tests hard-code the non-beta async methods that optionally send the +Foundry-Features header and verify both modes: + - allow_preview=True -> header is present with expected value + - allow_preview unset -> header is absent +""" + +import inspect +from typing import Any, ClassVar, Iterator, List, Tuple + +import pytest +from azure.core.pipeline.transport import AsyncHttpTransport +from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient + +from foundry_features_header_test_base import ( + FAKE_ENDPOINT, + AsyncFakeCredential, + FoundryFeaturesHeaderTestBase, + _RequestCaptured, +) + + +_NON_BETA_OPTIONAL_ASYNC_TEST_CASES = [ + # Each pytest.param entry has the following positional arguments: + # 1. method_name (str) – "." on AIProjectClient, e.g. "agents.create_version" + # The subclient and method names are parsed automatically from this string. + # 2. expected_header_value (str) – Expected value of the Foundry-Features header when allow_preview=True. + # Use a comma-separated list of feature=version pairs, e.g. "FeatureA=V1Preview,FeatureB=V1Preview". + # The test id is derived automatically from method_name. + pytest.param( + "agents.create_version", + "HostedAgents=V1Preview,WorkflowAgents=V1Preview", + ), + pytest.param( + "evaluation_rules.create_or_update", + "Evaluations=V1Preview", + ), +] + + +class CapturingAsyncTransport(AsyncHttpTransport): + """Async transport that captures the outgoing request and raises _RequestCaptured.""" + + async def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] + raise _RequestCaptured(request) + + async def open(self) -> None: + pass + + async def close(self) -> None: + pass + + async def __aenter__(self) -> "CapturingAsyncTransport": + return self + + async def __aexit__(self, *args: Any) -> None: + pass + + +@pytest.fixture(scope="module") +def async_client_preview_enabled() -> Iterator[AsyncAIProjectClient]: + yield AsyncAIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=AsyncFakeCredential(), # type: ignore[arg-type] + allow_preview=True, + transport=CapturingAsyncTransport(), + ) + + +@pytest.fixture(scope="module") +def async_client_preview_disabled() -> Iterator[AsyncAIProjectClient]: + yield AsyncAIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=AsyncFakeCredential(), # type: ignore[arg-type] + transport=CapturingAsyncTransport(), + ) + + +@pytest.fixture(scope="module", autouse=True) +def _print_report_optional_async() -> Iterator[None]: + """Print two Foundry-Features reports after all async optional tests finish: + one for the allow_preview=True test and one for the allow_preview-unset test. + """ + yield + present_report = TestFoundryFeaturesHeaderOptionalAsync._report + if present_report: + max_len = TestFoundryFeaturesHeaderOptionalAsync._report_max_label_len + print("\n\nFoundry-Features optional header report (async) — test_optional_header_present_when_preview_enabled_async:") + for label, header_value in sorted(present_report): + print(f"{label:<{max_len}} | \"{header_value}\"") + + absent_report = TestFoundryFeaturesHeaderOptionalAsync._report_absent + if absent_report: + max_len = TestFoundryFeaturesHeaderOptionalAsync._report_absent_max_label_len + print("\n\nFoundry-Features optional header report (async) — test_optional_header_absent_when_preview_not_enabled_async:") + for label, header_value in sorted(absent_report): + print(f"{label:<{max_len}} | \"{header_value}\"") + + +class TestFoundryFeaturesHeaderOptionalAsync(FoundryFeaturesHeaderTestBase): + """Async tests for optional Foundry-Features header behavior on non-beta methods.""" + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + _report_absent: ClassVar[List[Tuple[str, str]]] = [] + _report_absent_max_label_len: ClassVar[int] = 0 + + @staticmethod + async def _capture_async(call: Any) -> Any: + """Invoke *call()* and return the captured HttpRequest.""" + result = call() + + if inspect.isawaitable(result): + try: + await result + except _RequestCaptured as exc: + return exc.request + raise AssertionError("Transport was never called (awaitable completed without raising)") + + ai = result.__aiter__() + try: + await ai.__anext__() + except _RequestCaptured as exc: + return exc.request + except StopAsyncIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + + raise AssertionError("Transport was never called") + + @classmethod + async def _assert_header_present_async(cls, label: str, call: Any, expected_value: str) -> None: + request = await cls._capture_async(call) + cls._record_header_assertion(label, request, expected_value) + + @classmethod + async def _assert_header_absent_async(cls, label: str, call: Any) -> None: + request = await cls._capture_async(call) + cls._record_header_absence_assertion(label, request) + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "method_name,expected_header_value", _NON_BETA_OPTIONAL_ASYNC_TEST_CASES + ) + async def test_optional_header_present_when_preview_enabled_async( + self, + async_client_preview_enabled: AsyncAIProjectClient, + method_name: str, + expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + await self._assert_header_present_async(method_name, self._make_fake_call(method), expected_header_value) + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "method_name,_expected_header_value", _NON_BETA_OPTIONAL_ASYNC_TEST_CASES + ) + async def test_optional_header_absent_when_preview_not_enabled_async( + self, + async_client_preview_disabled: AsyncAIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_disabled, subclient_name) + method = getattr(sc, method_attr) + await self._assert_header_absent_async(method_name, self._make_fake_call(method)) From bf7b0add6d7c527dea8daf9f35ae69e5b5355f02 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Wed, 18 Mar 2026 09:57:28 -0700 Subject: [PATCH 18/36] Re-emit from TypeSpec, with support for api-key auth (#45748) --- sdk/ai/azure-ai-projects/.env.template | 1 + sdk/ai/azure-ai-projects/README.md | 52 ++++++++++- sdk/ai/azure-ai-projects/assets.json | 2 +- .../azure/ai/projects/_client.py | 15 ++- .../azure/ai/projects/_configuration.py | 26 ++++-- .../azure/ai/projects/_patch.py | 51 ++++++++--- .../azure/ai/projects/_utils/model_base.py | 6 ++ .../azure/ai/projects/aio/_client.py | 15 ++- .../azure/ai/projects/aio/_configuration.py | 26 ++++-- .../azure/ai/projects/aio/_patch.py | 43 ++++++--- .../ai/projects/aio/operations/_operations.py | 1 + .../ai/projects/aio/operations/_patch.py | 8 +- .../azure/ai/projects/models/_enums.py | 2 + .../azure/ai/projects/models/_models.py | 2 +- .../ai/projects/operations/_operations.py | 9 +- .../azure/ai/projects/operations/_patch.py | 8 +- .../samples/agents/sample_agent_basic.py | 3 +- .../agents/sample_agent_basic_api_key_auth.py | 84 +++++++++++++++++ .../sample_agent_basic_api_key_auth_async.py | 91 +++++++++++++++++++ .../agents/sample_agent_basic_async.py | 3 +- .../tests/agents/test_agent_responses_crud.py | 50 ++++++++++ .../agents/test_agent_responses_crud_async.py | 53 +++++++++++ sdk/ai/azure-ai-projects/tests/conftest.py | 6 +- .../foundry_features_header_test_base.py | 31 +++++-- .../test_foundry_features_header.py | 5 +- .../test_foundry_features_header_async.py | 3 +- .../test_foundry_features_header_optional.py | 27 ++---- ..._foundry_features_header_optional_async.py | 39 +++----- sdk/ai/azure-ai-projects/tests/test_base.py | 20 +++- 29 files changed, 552 insertions(+), 130 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth.py create mode 100644 sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth_async.py diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index 620367d29578..6ffe61b712d7 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -21,6 +21,7 @@ AZURE_AI_PROJECTS_CONSOLE_LOGGING= # Project endpoint has the format: # `https://.services.ai.azure.com/api/projects/` FOUNDRY_PROJECT_ENDPOINT= +FOUNDRY_PROJECT_API_KEY= FOUNDRY_MODEL_NAME= FOUNDRY_AGENT_NAME= CONVERSATION_ID= diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 93d3cd788fb8..8bb065bc3893 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -55,7 +55,8 @@ To report an issue with the client library, or request additional features, plea * An [Azure subscription][azure_sub]. * A [project in Microsoft Foundry](https://learn.microsoft.com/azure/foundry/how-to/create-projects). * A Foundry project endpoint URL of the form `https://your-ai-services-account-name.services.ai.azure.com/api/projects/your-project-name`. It can be found in your Microsoft Foundry Project home page. Below we will assume the environment variable `FOUNDRY_PROJECT_ENDPOINT` was defined to hold this value. -* An Entra ID token for authentication. Your application needs an object that implements the [TokenCredential](https://learn.microsoft.com/python/api/azure-core/azure.core.credentials.tokencredential) interface. Code samples here use [DefaultAzureCredential](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential). To get that working, you will need: +* To authenticate using API key, you will need the "Project API key" as shown in your Microsoft Foundry Project home page. +* To authenticate using Entra ID, your application needs an object that implements the [TokenCredential](https://learn.microsoft.com/python/api/azure-core/azure.core.credentials.tokencredential) interface. Code samples here use [DefaultAzureCredential](https://learn.microsoft.com/python/api/azure-identity/azure.identity.defaultazurecredential). To get that working, you will need: * An appropriate role assignment. See [Role-based access control in Microsoft Foundry portal](https://learn.microsoft.com/azure/foundry/concepts/rbac-foundry). Role assignment can be done via the "Access Control (IAM)" tab of your Azure AI Project resource in the Azure portal. * [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed. * You are logged into your Azure account by running `az login`. @@ -74,9 +75,46 @@ pip show azure-ai-projects ## Key concepts -### Create and authenticate the client with Entra ID +### Create and authenticate the client with API key + +To construct a synchronous client using a context manager: + +```python +import os +from azure.core.credentials import AzureKeyCredential +from azure.ai.projects import AIProjectClient + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +api_key = os.environ["FOUNDRY_PROJECT_API_KEY"] + +with ( + AIProjectClient(endpoint=endpoint, credential=AzureKeyCredential(api_key)) as project_client +): +``` -Entra ID is the only authentication method supported at the moment by the client. +To construct an asynchronous client, install the additional package [aiohttp](https://pypi.org/project/aiohttp/): + +```bash +pip install aiohttp +``` + +and run: + +```python +import os +import asyncio +from azure.core.credentials import AzureKeyCredential +from azure.ai.projects.aio import AIProjectClient + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +api_key = os.environ["FOUNDRY_PROJECT_API_KEY"] + +async with ( + AIProjectClient(endpoint=endpoint, credential=AzureKeyCredential(api_key)) as project_client +): +``` + +### Create and authenticate the client with Entra ID To construct a synchronous client using a context manager: @@ -85,9 +123,11 @@ import os from azure.ai.projects import AIProjectClient from azure.identity import DefaultAzureCredential +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, ): ``` @@ -105,9 +145,11 @@ import asyncio from azure.ai.projects.aio import AIProjectClient from azure.identity.aio import DefaultAzureCredential +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + async with ( DefaultAzureCredential() as credential, - AIProjectClient(endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], credential=credential) as project_client, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, ): ``` diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 4d184f12bdb4..acad4b35c8fe 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_5b25ba9450" + "Tag": "python/ai/azure-ai-projects_20150d1e9d" } diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py index c9b4ba135893..635a13982e7b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py @@ -7,10 +7,11 @@ # -------------------------------------------------------------------------- from copy import deepcopy -from typing import Any, Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING, Union from typing_extensions import Self from azure.core import PipelineClient +from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import policies from azure.core.rest import HttpRequest, HttpResponse @@ -53,8 +54,10 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". Required. :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Required. - :type credential: ~azure.core.credentials.TokenCredential + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials.TokenCredential :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool @@ -65,7 +68,11 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes """ def __init__( - self, endpoint: str, credential: "TokenCredential", allow_preview: Optional[bool] = None, **kwargs: Any + self, + endpoint: str, + credential: Union[AzureKeyCredential, "TokenCredential"], + allow_preview: Optional[bool] = None, + **kwargs: Any ) -> None: _endpoint = "{endpoint}" self._config = AIProjectClientConfiguration( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py index 1b9d513e9220..25c0f861fa8b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py @@ -6,8 +6,9 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -from typing import Any, Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING, Union +from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import policies from ._version import VERSION @@ -28,8 +29,10 @@ class AIProjectClientConfiguration: # pylint: disable=too-many-instance-attribu the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". Required. :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Required. - :type credential: ~azure.core.credentials.TokenCredential + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials.TokenCredential :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool @@ -40,7 +43,11 @@ class AIProjectClientConfiguration: # pylint: disable=too-many-instance-attribu """ def __init__( - self, endpoint: str, credential: "TokenCredential", allow_preview: Optional[bool] = None, **kwargs: Any + self, + endpoint: str, + credential: Union[AzureKeyCredential, "TokenCredential"], + allow_preview: Optional[bool] = None, + **kwargs: Any, ) -> None: api_version: str = kwargs.pop("api_version", "v1") @@ -58,6 +65,13 @@ def __init__( self.polling_interval = kwargs.get("polling_interval", 30) self._configure(**kwargs) + def _infer_policy(self, **kwargs): + if isinstance(self.credential, AzureKeyCredential): + return policies.AzureKeyCredentialPolicy(self.credential, "api-key", **kwargs) + if hasattr(self.credential, "get_token"): + return policies.BearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) + raise TypeError(f"Unsupported credential: {self.credential}") + def _configure(self, **kwargs: Any) -> None: self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs) self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs) @@ -69,6 +83,4 @@ def _configure(self, **kwargs: Any) -> None: self.retry_policy = kwargs.get("retry_policy") or policies.RetryPolicy(**kwargs) self.authentication_policy = kwargs.get("authentication_policy") if self.credential and not self.authentication_policy: - self.authentication_policy = policies.BearerTokenCredentialPolicy( - self.credential, *self.credential_scopes, **kwargs - ) + self.authentication_policy = self._infer_policy(**kwargs) 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 98c3e388bb92..9d5ca36cce62 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -11,9 +11,10 @@ import os import re import logging -from typing import List, Any +from typing import List, Any, Union import httpx # pylint: disable=networking-import-outside-azure-core-transport from openai import OpenAI +from azure.core.credentials import AzureKeyCredential from azure.core.tracing.decorator import distributed_trace from azure.core.credentials import TokenCredential from azure.identity import get_bearer_token_provider @@ -46,8 +47,10 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". Required. :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Required. - :type credential: ~azure.core.credentials.TokenCredential + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials.TokenCredential :param allow_preview: Whether to enable preview features. Optional, default is False. Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). @@ -64,7 +67,12 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins """ def __init__( - self, endpoint: str, credential: TokenCredential, *, allow_preview: bool = False, **kwargs: Any + self, + endpoint: str, + credential: Union[AzureKeyCredential, "TokenCredential"], + *, + allow_preview: bool = False, + **kwargs: Any, ) -> None: self._console_logging_enabled: bool = ( @@ -78,7 +86,7 @@ def __init__( azure_logger = logging.getLogger("azure") azure_logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler(stream=sys.stdout) - console_handler.addFilter(_BearerTokenRedactionFilter()) + console_handler.addFilter(_AuthSecretsFilter()) azure_logger.addHandler(console_handler) # Exclude detailed logs for network calls associated with getting Entra ID token. logging.getLogger("azure.identity").setLevel(logging.ERROR) @@ -105,8 +113,12 @@ def get_openai_client(self, **kwargs: Any) -> "OpenAI": # type: ignore[name-def The OpenAI client constructor is called with: * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. Can be overridden by passing ``base_url`` as a 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". + * If :class:`~azure.ai.projects.AIProjectClient` was constructed with a bearer token, ``api_key`` is set + to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient + constructor, with scope ``https://ai.azure.com/.default``. + Can be overridden by passing ``api_key`` as a keyword argument. + * If :class:`~azure.ai.projects.AIProjectClient` was constructed with ``api-key``, it is passed to the + OpenAI constructor as is. Can be overridden by passing ``api_key`` as a keyword argument. .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. @@ -130,13 +142,17 @@ def get_openai_client(self, **kwargs: Any) -> "OpenAI": # type: ignore[name-def base_url, ) - # Allow caller to override api_key, otherwise use token provider + # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor if "api_key" in kwargs: api_key = kwargs.pop("api_key") else: - api_key = get_bearer_token_provider( - self._config.credential, # pylint: disable=protected-access - "https://ai.azure.com/.default", + api_key = ( + self._config.credential.key # pylint: disable=protected-access + if isinstance(self._config.credential, AzureKeyCredential) + else get_bearer_token_provider( + self._config.credential, # pylint: disable=protected-access + "https://ai.azure.com/.default", + ) ) if "http_client" in kwargs: @@ -178,16 +194,21 @@ def _create_openai_client(**kwargs) -> OpenAI: return client -class _BearerTokenRedactionFilter(logging.Filter): - """Redact bearer tokens in azure.core log messages before they are emitted to console.""" +class _AuthSecretsFilter(logging.Filter): + """Redact bearer tokens and api-key values in azure.core log messages before they are emitted to console.""" _AUTH_HEADER_DICT_PATTERN = re.compile( - r"(?i)(['\"]authorization['\"]\s*:\s*['\"])bearer\s+[^'\"]+(['\"])", + r"(?i)(['\"]authorization['\"]\ *:\ *['\"])bearer\s+[^'\"]+(['\"])", + ) + + _API_KEY_HEADER_DICT_PATTERN = re.compile( + r"(?i)(['\"]api-key['\"]\ *:\ *['\"])[^'\"]+(['\"])", ) def filter(self, record: logging.LogRecord) -> bool: rendered = record.getMessage() redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) + redacted = self._API_KEY_HEADER_DICT_PATTERN.sub(r"\1\2", redacted) if redacted != rendered: # Replace the pre-formatted content so handlers emit sanitized output. record.msg = redacted @@ -208,7 +229,7 @@ class OpenAILoggingTransport(httpx.HTTPTransport): """ def _sanitize_auth_header(self, headers) -> None: - """Sanitize authorization header by redacting sensitive information. + """Sanitize authorization and api-key headers by redacting sensitive information. :param headers: Dictionary of HTTP headers to sanitize :type headers: dict diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_utils/model_base.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_utils/model_base.py index 9616929f7415..a75a22adbb97 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_utils/model_base.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_utils/model_base.py @@ -630,6 +630,9 @@ def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: if len(items) > 0: existed_attr_keys.append(xml_name) dict_to_pass[rf._rest_name] = _deserialize(rf._type, items) + elif not rf._is_optional: + existed_attr_keys.append(xml_name) + dict_to_pass[rf._rest_name] = [] continue # text element is primitive type @@ -905,6 +908,8 @@ def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-retur # is it optional? try: if any(a is _NONE_TYPE for a in annotation.__args__): # pyright: ignore + if rf: + rf._is_optional = True if len(annotation.__args__) <= 2: # pyright: ignore if_obj_deserializer = _get_deserialize_callable_from_annotation( next(a for a in annotation.__args__ if a is not _NONE_TYPE), module, rf # pyright: ignore @@ -1084,6 +1089,7 @@ def __init__( self._is_discriminator = is_discriminator self._visibility = visibility self._is_model = False + self._is_optional = False self._default = default self._format = format self._is_multipart_file_input = is_multipart_file_input diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py index 086ddbab3fd9..3fadfc6efc2f 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py @@ -7,10 +7,11 @@ # -------------------------------------------------------------------------- from copy import deepcopy -from typing import Any, Awaitable, Optional, TYPE_CHECKING +from typing import Any, Awaitable, Optional, TYPE_CHECKING, Union from typing_extensions import Self from azure.core import AsyncPipelineClient +from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import policies from azure.core.rest import AsyncHttpResponse, HttpRequest @@ -53,8 +54,10 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". Required. :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Required. - :type credential: ~azure.core.credentials_async.AsyncTokenCredential + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials_async.AsyncTokenCredential :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool @@ -65,7 +68,11 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes """ def __init__( - self, endpoint: str, credential: "AsyncTokenCredential", allow_preview: Optional[bool] = None, **kwargs: Any + self, + endpoint: str, + credential: Union[AzureKeyCredential, "AsyncTokenCredential"], + allow_preview: Optional[bool] = None, + **kwargs: Any ) -> None: _endpoint = "{endpoint}" self._config = AIProjectClientConfiguration( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py index 7fd12934a2a8..bd8d890b071b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py @@ -6,8 +6,9 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -from typing import Any, Optional, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING, Union +from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import policies from .._version import VERSION @@ -28,8 +29,10 @@ class AIProjectClientConfiguration: # pylint: disable=too-many-instance-attribu the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". Required. :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Required. - :type credential: ~azure.core.credentials_async.AsyncTokenCredential + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials_async.AsyncTokenCredential :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool @@ -40,7 +43,11 @@ class AIProjectClientConfiguration: # pylint: disable=too-many-instance-attribu """ def __init__( - self, endpoint: str, credential: "AsyncTokenCredential", allow_preview: Optional[bool] = None, **kwargs: Any + self, + endpoint: str, + credential: Union[AzureKeyCredential, "AsyncTokenCredential"], + allow_preview: Optional[bool] = None, + **kwargs: Any, ) -> None: api_version: str = kwargs.pop("api_version", "v1") @@ -58,6 +65,13 @@ def __init__( self.polling_interval = kwargs.get("polling_interval", 30) self._configure(**kwargs) + def _infer_policy(self, **kwargs): + if isinstance(self.credential, AzureKeyCredential): + return policies.AzureKeyCredentialPolicy(self.credential, "api-key", **kwargs) + if hasattr(self.credential, "get_token"): + return policies.AsyncBearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) + raise TypeError(f"Unsupported credential: {self.credential}") + def _configure(self, **kwargs: Any) -> None: self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs) self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs) @@ -69,6 +83,4 @@ def _configure(self, **kwargs: Any) -> None: self.retry_policy = kwargs.get("retry_policy") or policies.AsyncRetryPolicy(**kwargs) self.authentication_policy = kwargs.get("authentication_policy") if self.credential and not self.authentication_policy: - self.authentication_policy = policies.AsyncBearerTokenCredentialPolicy( - self.credential, *self.credential_scopes, **kwargs - ) + self.authentication_policy = self._infer_policy(**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 4f8312c6996e..7172df9225b7 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 @@ -10,13 +10,14 @@ import os import logging -from typing import List, Any +from typing import List, Any, Union import httpx # pylint: disable=networking-import-outside-azure-core-transport from openai import AsyncOpenAI +from azure.core.credentials import AzureKeyCredential from azure.core.tracing.decorator import distributed_trace from azure.core.credentials_async import AsyncTokenCredential from azure.identity.aio import get_bearer_token_provider -from .._patch import _BearerTokenRedactionFilter +from .._patch import _AuthSecretsFilter from ._client import AIProjectClient as AIProjectClientGenerated from .operations import TelemetryOperations @@ -46,8 +47,10 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". Required. :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Required. - :type credential: ~azure.core.credentials_async.AsyncTokenCredential + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials_async.AsyncTokenCredential :param allow_preview: Whether to enable preview features. Optional, default is False. Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). @@ -56,6 +59,7 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` with the appropriate value in all relevant calls to the service. + :type allow_preview: bool :keyword api_version: The API version to use for this operation. Known values are "v1" and None. Default value is "v1". Note that overriding this default value may result in unsupported behavior. @@ -63,7 +67,12 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins """ def __init__( - self, endpoint: str, credential: AsyncTokenCredential, *, allow_preview: bool = False, **kwargs: Any + self, + endpoint: str, + credential: Union[AzureKeyCredential, "AsyncTokenCredential"], + *, + allow_preview: bool = False, + **kwargs: Any, ) -> None: self._console_logging_enabled: bool = ( @@ -77,7 +86,7 @@ def __init__( azure_logger = logging.getLogger("azure") azure_logger.setLevel(logging.DEBUG) console_handler = logging.StreamHandler(stream=sys.stdout) - console_handler.addFilter(_BearerTokenRedactionFilter()) + console_handler.addFilter(_AuthSecretsFilter()) azure_logger.addHandler(console_handler) # Exclude detailed logs for network calls associated with getting Entra ID token. logging.getLogger("azure.identity").setLevel(logging.ERROR) @@ -104,8 +113,12 @@ def get_openai_client(self, **kwargs: Any) -> "AsyncOpenAI": # type: ignore[nam The AsyncOpenAI client constructor is called with: * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. Can be overridden by passing ``base_url`` as a 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". + * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with a bearer token, ``api_key`` is set + to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient + constructor, with scope ``https://ai.azure.com/.default``. + Can be overridden by passing ``api_key`` as a keyword argument. + * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with ``api-key``, it is passed to the + OpenAI constructor as is. Can be overridden by passing ``api_key`` as a keyword argument. .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. @@ -129,13 +142,17 @@ def get_openai_client(self, **kwargs: Any) -> "AsyncOpenAI": # type: ignore[nam base_url, ) - # Allow caller to override api_key, otherwise use token provider + # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor if "api_key" in kwargs: api_key = kwargs.pop("api_key") else: - api_key = get_bearer_token_provider( - self._config.credential, # pylint: disable=protected-access - "https://ai.azure.com/.default", + api_key = ( + self._config.credential.key # pylint: disable=protected-access + if isinstance(self._config.credential, AzureKeyCredential) + else get_bearer_token_provider( + self._config.credential, # pylint: disable=protected-access + "https://ai.azure.com/.default", + ) ) if "http_client" in kwargs: @@ -190,7 +207,7 @@ class OpenAILoggingTransport(httpx.AsyncHTTPTransport): """ def _sanitize_auth_header(self, headers): - """Sanitize authorization header by redacting sensitive information. + """Sanitize authorization and api-key headers by redacting sensitive information. :param headers: Dictionary of HTTP headers to sanitize :type headers: dict diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 5daa5483b071..3e78f121e181 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -512,6 +512,7 @@ async def create_version( :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index fd4e68774f3b..9e5b1872d76d 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -19,9 +19,10 @@ BetaEvaluationTaxonomiesOperations, BetaEvaluatorsOperations, BetaInsightsOperations, + BetaOperations as GeneratedBetaOperations, BetaRedTeamsOperations, BetaSchedulesOperations, - BetaOperations as GeneratedBetaOperations, + BetaToolsetsOperations, ) @@ -47,6 +48,8 @@ class BetaOperations(GeneratedBetaOperations): """:class:`~azure.ai.projects.aio.operations.BetaRedTeamsOperations` operations""" schedules: BetaSchedulesOperations """:class:`~azure.ai.projects.aio.operations.BetaSchedulesOperations` operations""" + toolsets: BetaToolsetsOperations + """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -57,15 +60,16 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: __all__: List[str] = [ "AgentsOperations", "BetaEvaluationTaxonomiesOperations", - "EvaluationRulesOperations", "BetaEvaluatorsOperations", "BetaInsightsOperations", "BetaMemoryStoresOperations", "BetaOperations", "BetaRedTeamsOperations", "BetaSchedulesOperations", + "BetaToolsetsOperations", "ConnectionsOperations", "DatasetsOperations", + "EvaluationRulesOperations", "TelemetryOperations", ] # Add all objects you want publicly available to users at this package level diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index 923dd800ef60..fff2adb637c4 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -373,6 +373,8 @@ class _FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): """MEMORY_STORES_V1_PREVIEW.""" TOOLSET_V1_PREVIEW = "Toolsets=V1Preview" """TOOLSET_V1_PREVIEW.""" + AGENT_ENDPOINT_V1_PREVIEW = "AgentEndpoints=V1Preview" + """AGENT_ENDPOINT_V1_PREVIEW.""" class FunctionShellToolParamEnvironmentType(str, Enum, metaclass=CaseInsensitiveEnumMeta): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index f009264826d7..2974e60e06a3 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -2792,7 +2792,7 @@ class ContainerNetworkPolicyAllowlistParam(ContainerNetworkPolicyParam, discrimi allowed_domains: list[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of allowed domains when type is ``allowlist``. Required.""" domain_secrets: Optional[list["_models.ContainerNetworkPolicyDomainSecretParam"]] = rest_field( - visibility=["read", "create", "update", "delete", "query"] + visibility=["create"] ) """Optional domain-scoped secrets for allowlisted domains.""" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index 5a9b2fc628c9..55f80af3bb5c 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -43,6 +43,7 @@ [ _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, + _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, ] ) @@ -142,7 +143,12 @@ def build_agents_list_request( def build_agents_create_version_request( - agent_name: str, *, foundry_features: Optional[Union[str, _AgentDefinitionOptInKeys]] = None, **kwargs: Any + agent_name: str, + *, + foundry_features: Optional[ + Union[str, _AgentDefinitionOptInKeys, Literal[_FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW]] + ] = None, + **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -2348,6 +2354,7 @@ def create_version( :raises ~azure.core.exceptions.HttpResponseError: """ _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore + error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index bc78f4d6baf8..de30f901a967 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -19,9 +19,10 @@ BetaEvaluationTaxonomiesOperations, BetaEvaluatorsOperations, BetaInsightsOperations, + BetaOperations as GeneratedBetaOperations, BetaRedTeamsOperations, BetaSchedulesOperations, - BetaOperations as GeneratedBetaOperations, + BetaToolsetsOperations, ) @@ -47,6 +48,8 @@ class BetaOperations(GeneratedBetaOperations): """:class:`~azure.ai.projects.operations.BetaRedTeamsOperations` operations""" schedules: BetaSchedulesOperations """:class:`~azure.ai.projects.operations.BetaSchedulesOperations` operations""" + toolsets: BetaToolsetsOperations + """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) @@ -57,15 +60,16 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: __all__: List[str] = [ "AgentsOperations", "BetaEvaluationTaxonomiesOperations", - "EvaluationRulesOperations", "BetaEvaluatorsOperations", "BetaInsightsOperations", "BetaMemoryStoresOperations", "BetaOperations", "BetaRedTeamsOperations", "BetaSchedulesOperations", + "BetaToolsetsOperations", "ConnectionsOperations", "DatasetsOperations", + "EvaluationRulesOperations", "TelemetryOperations", ] # Add all objects you want publicly available to users at this package level diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py index 2f097665de87..64b3161d4af6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic.py @@ -6,7 +6,8 @@ """ DESCRIPTION: This sample demonstrates how to run basic Prompt Agent operations - using the synchronous AIProjectClient. + using the synchronous AIProjectClient. It uses Entra ID authentication to + connect to the Microsoft Foundry service. The OpenAI compatible Responses and Conversation calls in this sample are made using the OpenAI client from the `openai` package. See https://platform.openai.com/docs/api-reference diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth.py new file mode 100644 index 000000000000..2f765a6ec152 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth.py @@ -0,0 +1,84 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to run basic Prompt Agent operations + using the synchronous AIProjectClient. It uses API key authentication to + connect to the Microsoft Foundry service. + + Note that API key authentication is available starting with version + 2.0.2 of the client library. + + The OpenAI compatible Responses and Conversation calls in this sample are made using + the OpenAI client from the `openai` package. See https://platform.openai.com/docs/api-reference + for more information. + +USAGE: + python sample_agent_basic_api_key_auth.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.2" python-dotenv + + Set these environment variables with your own values: + 2) FOUNDRY_PROJECT_API_KEY - The Project API key as found in your Foundry project home page. + 3) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +from dotenv import load_dotenv +from azure.core.credentials import AzureKeyCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +api_key = os.environ["FOUNDRY_PROJECT_API_KEY"] + +with ( + AIProjectClient(endpoint=endpoint, credential=AzureKeyCredential(api_key)) as project_client, + project_client.get_openai_client() as openai_client, +): + + agent = project_client.agents.create_version( + agent_name="MyAgent", + definition=PromptAgentDefinition( + model=os.environ["FOUNDRY_MODEL_NAME"], + instructions="You are a helpful assistant that answers general questions", + ), + ) + print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + conversation = openai_client.conversations.create( + items=[{"type": "message", "role": "user", "content": "What is the size of France in square miles?"}], + ) + print(f"Created conversation with initial user message (id: {conversation.id})") + + response = openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {response.output_text}") + + openai_client.conversations.items.create( + conversation_id=conversation.id, + items=[{"type": "message", "role": "user", "content": "And what is the capital city?"}], + ) + print("Added a second user message to the conversation") + + response = openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {response.output_text}") + + openai_client.conversations.delete(conversation_id=conversation.id) + print("Conversation deleted") + + project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth_async.py new file mode 100644 index 000000000000..ba7c0d2acba6 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_api_key_auth_async.py @@ -0,0 +1,91 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to run basic Prompt Agent operations + using the asynchronous AIProjectClient. It uses API key authentication to + connect to the Microsoft Foundry service. + + Note that API key authentication is available starting with version + 2.0.2 of the client library. + + The OpenAI compatible Responses and Conversation calls in this sample are made using + the OpenAI client from the `openai` package. See https://platform.openai.com/docs/api-reference + for more information. + +USAGE: + python sample_agent_basic_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.2" python-dotenv aiohttp + + Set these environment variables with your own values: + 2) FOUNDRY_PROJECT_API_KEY - The Project API key as found in your Foundry project home page. + 3) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import asyncio +import os +from dotenv import load_dotenv +from azure.core.credentials import AzureKeyCredential +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +api_key = os.environ["FOUNDRY_PROJECT_API_KEY"] + + +async def main() -> None: + async with ( + AIProjectClient(endpoint=endpoint, credential=AzureKeyCredential(api_key)) as project_client, + project_client.get_openai_client() as openai_client, + ): + + agent = await project_client.agents.create_version( + agent_name="MyAgent", + definition=PromptAgentDefinition( + model=os.environ["FOUNDRY_MODEL_NAME"], + instructions="You are a helpful assistant that answers general questions.", + ), + ) + print(f"Agent created (name: {agent.name}, id: {agent.id}, version: {agent.version})") + + conversation = await openai_client.conversations.create( + items=[{"type": "message", "role": "user", "content": "What is the size of France in square miles?"}], + ) + print(f"Created conversation with initial user message (id: {conversation.id})") + + response = await openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {response.output_text}") + + await openai_client.conversations.items.create( + conversation_id=conversation.id, + items=[{"type": "message", "role": "user", "content": "And what is the capital city?"}], + ) + print("Added a second user message to the conversation") + + response = await openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response output: {response.output_text}") + + await openai_client.conversations.delete(conversation_id=conversation.id) + print("Conversation deleted") + + await project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py index 0f9d39bc6685..3936a1aec2e6 100644 --- a/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_agent_basic_async.py @@ -6,7 +6,8 @@ """ DESCRIPTION: This sample demonstrates how to run basic Prompt Agent operations - using the asynchronous AIProjectClient. + using the asynchronous AIProjectClient. It uses Entra ID authentication to + connect to the Microsoft Foundry service. The OpenAI compatible Responses and Conversation calls in this sample are made using the OpenAI client from the `openai` package. See https://platform.openai.com/docs/api-reference diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py index 4a8727c0e5e8..abacd768b13d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud.py @@ -152,6 +152,56 @@ def test_agent_responses_crud(self, **kwargs): project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) print("Agent deleted") + # To run this test: + # pytest tests/agents/test_agent_responses_crud.py::TestAgentResponsesCrud::test_aget_response_curd_api_key_auth -s + @servicePreparer() + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_aget_response_curd_api_key_auth(self, **kwargs): + + model = kwargs.get("foundry_model_name") + project_client = self.create_client(use_api_key=True, **kwargs) + openai_client = project_client.get_openai_client() + + agent = project_client.agents.create_version( + agent_name="MyAgent", + definition=PromptAgentDefinition( + model=model, + instructions="You are a helpful assistant that answers general questions", + ), + ) + print(f"\nAgent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + conversation = openai_client.conversations.create( + items=[{"type": "message", "role": "user", "content": "How many feet in a mile?"}] + ) + print(f"Created conversation with initial user message (id: {conversation.id})") + + response = openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response id: {response.id}, output text: {response.output_text}") + assert "5280" in response.output_text or "5,280" in response.output_text + + _ = openai_client.conversations.items.create( + conversation.id, + items=[{"type": "message", "role": "user", "content": "And how many meters?"}], + ) + + response = openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response id: {response.id}, output text: {response.output_text}") + assert "1609" in response.output_text or "1,609" in response.output_text + + # Teardown + openai_client.conversations.delete(conversation_id=conversation.id) + print("Conversation deleted") + + project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") + # To run this test: # pytest tests\agents\test_agent_responses_crud.py::TestAgentResponsesCrud::test_agent_responses_with_structured_output -s @servicePreparer() diff --git a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py index 22aeff937bc5..b76c6c676994 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/test_agent_responses_crud_async.py @@ -123,6 +123,59 @@ async def test_agent_responses_crud_async(self, **kwargs): await project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) print("Agent deleted") + # To run this test: + # pytest tests/agents/test_agent_responses_crud_async.py::TestAgentResponsesCrudAsync::test_aget_response_curd_api_key_auth_async -s + @servicePreparer() + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_aget_response_curd_api_key_auth_async(self, **kwargs): + + model = kwargs.get("foundry_model_name") + project_client = self.create_async_client(operation_group="agents", use_api_key=True, **kwargs) + openai_client = project_client.get_openai_client() + + async with project_client: + + agent = await project_client.agents.create_version( + agent_name="MyAgent", + definition=PromptAgentDefinition( + model=model, + instructions="You are a helpful assistant that answers general questions", + ), + ) + print(f"\nAgent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + conversation = await openai_client.conversations.create( + items=[{"type": "message", "role": "user", "content": "How many feet in a mile?"}] + ) + print(f"Created conversation with initial user message (id: {conversation.id})") + + response = await openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response id: {response.id}, output text: {response.output_text}") + assert "5280" in response.output_text or "5,280" in response.output_text + + await openai_client.conversations.items.create( + conversation_id=conversation.id, + items=[{"type": "message", "role": "user", "content": "And how many meters?"}], + ) + print("Added a second user message to the conversation") + + response = await openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": agent.name, "type": "agent_reference"}}, + ) + print(f"Response id: {response.id}, output text: {response.output_text}") + assert "1609" in response.output_text or "1,609" in response.output_text + + # Teardown + await openai_client.conversations.delete(conversation_id=conversation.id) + print("Conversation deleted") + + await project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") + # To run this test: # pytest tests\agents\test_agent_responses_crud_async.py::TestAgentResponsesCrudAsync::test_agent_responses_with_structured_output_async -s @servicePreparer() diff --git a/sdk/ai/azure-ai-projects/tests/conftest.py b/sdk/ai/azure-ai-projects/tests/conftest.py index 483f6fd7f4eb..a6dd76036377 100644 --- a/sdk/ai/azure-ai-projects/tests/conftest.py +++ b/sdk/ai/azure-ai-projects/tests/conftest.py @@ -48,6 +48,7 @@ class SanitizedValues: PROJECT_NAME = "sanitized-project-name" COMPONENT_NAME = "sanitized-component-name" AGENTS_API_VERSION = "sanitized-api-version" + API_KEY = "sanitized-api-key" @pytest.fixture(scope="session") @@ -59,6 +60,7 @@ def sanitized_values(): "account_name": f"{SanitizedValues.ACCOUNT_NAME}", "component_name": f"{SanitizedValues.COMPONENT_NAME}", "agents_api_version": f"{SanitizedValues.AGENTS_API_VERSION}", + "api_key": f"{SanitizedValues.API_KEY}", } @@ -153,6 +155,8 @@ def sanitize_url_paths(): ) add_body_string_sanitizer(target=image_generation_model, value="sanitized-gpt-image") + add_header_regex_sanitizer(key="api-key", value=SanitizedValues.API_KEY) + # Deterministic fallback sanitization for image generation deployment/model values. # These do not depend on environment variables and ensure recordings are redacted even # when runtime values come from unexpected sources. @@ -167,7 +171,7 @@ def sanitize_url_paths(): ) # Sanitize API key from service response (this includes Application Insights connection string) - add_body_key_sanitizer(json_path="credentials.key", value="sanitized-api-key") + add_body_key_sanitizer(json_path="credentials.key", value=SanitizedValues.API_KEY) # Sanitize GitHub personal access tokens that may appear in connection credentials add_general_regex_sanitizer(regex=r"github_pat_[A-Za-z0-9_]+", value="sanitized-github-pat") diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py index 22cf12076aa6..efb53002ddf2 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py @@ -16,6 +16,7 @@ """ import inspect +import pytest from typing import Any, ClassVar, List, Tuple, Union, get_origin from azure.core.credentials import AccessToken @@ -41,11 +42,30 @@ "toolsets": "Toolsets=V1Preview", } +# Shared test cases for non-beta methods that optionally send the Foundry-Features header. +# Used by both test_foundry_features_header_optional.py (sync) and +# test_foundry_features_header_optional_async.py (async). +_NON_BETA_OPTIONAL_TEST_CASES = [ + # Each pytest.param entry has the following positional arguments: + # 1. method_name (str) – "." on AIProjectClient, e.g. "agents.create_version" + # The subclient and method names are parsed automatically from this string. + # 2. expected_header_value (str) – Expected value of the Foundry-Features header when allow_preview=True. + # Use a comma-separated list of feature=version pairs, e.g. "FeatureA=V1Preview,FeatureB=V1Preview". + # The test id is derived automatically from method_name. + pytest.param( + "agents.create_version", + "HostedAgents=V1Preview,WorkflowAgents=V1Preview,AgentEndpoints=V1Preview", + ), + pytest.param( + "evaluation_rules.create_or_update", + "Evaluations=V1Preview", + ), +] + # Both sentinel values – used by _make_fake_call to detect required parameters # whose defaults are the internal _Unset object (rather than inspect.Parameter.empty). _UNSET_SENTINELS: frozenset = frozenset({_SyncUnset, _AsyncUnset}) - # --------------------------------------------------------------------------- # Sentinel exception raised by capturing transports # --------------------------------------------------------------------------- @@ -147,9 +167,7 @@ def _make_fake_call(cls, method: Any) -> Any: if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD): continue - is_required = ( - param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS - ) + is_required = param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS if not is_required: continue @@ -176,8 +194,7 @@ def _record_header_assertion(cls, label: str, request: Any, expected_value: str) f"missing or empty.\nActual headers: {dict(request.headers)}" ) assert header_value == expected_value, ( - f"{label}: expected '{FOUNDRY_FEATURES_HEADER}: {expected_value}' " - f"but got '{header_value}'" + f"{label}: expected '{FOUNDRY_FEATURES_HEADER}: {expected_value}' " f"but got '{header_value}'" ) cls._report_max_label_len = max(cls._report_max_label_len, len(label)) cls._report.append((label, header_value)) @@ -192,6 +209,6 @@ def _record_header_absence_assertion(cls, label: str, request: Any) -> None: f"{label}: expected '{FOUNDRY_FEATURES_HEADER}' header to be absent.\n" f"Actual headers: {dict(request.headers)}" ) - absence_note = f'\'{FOUNDRY_FEATURES_HEADER}\' header not present (as expected)' + absence_note = f"'{FOUNDRY_FEATURES_HEADER}' header not present (as expected)" cls._report_absent_max_label_len = max(cls._report_absent_max_label_len, len(label)) cls._report_absent.append((label, absence_note)) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py index 71fdf4fd4212..b9890940a9da 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py @@ -45,7 +45,6 @@ _RequestCaptured, ) - # --------------------------------------------------------------------------- # Sync-specific transport # --------------------------------------------------------------------------- @@ -146,7 +145,7 @@ def _print_report() -> Iterator[None]: if report: print("\n\nFoundry-Features header report (sync):") for label, header_value in sorted(report): - print(f"{label:<{max_len}} | \"{header_value}\"") + print(f'{label:<{max_len}} | "{header_value}"') # --------------------------------------------------------------------------- @@ -209,4 +208,4 @@ def test_foundry_features_header( """Assert that *method_name* on .beta. sends the expected Foundry-Features value.""" sc = getattr(client.beta, subclient_name) method = getattr(sc, method_name) - self._assert_header(label, self._make_fake_call(method), expected_header_value) \ No newline at end of file + self._assert_header(label, self._make_fake_call(method), expected_header_value) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py index 73db8c64491d..9cadf09a6910 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py @@ -47,7 +47,6 @@ _RequestCaptured, ) - # --------------------------------------------------------------------------- # Async-specific transport # --------------------------------------------------------------------------- @@ -156,7 +155,7 @@ def _print_report_async() -> Iterator[None]: if report: print("\n\nFoundry-Features header report (async):") for label, header_value in sorted(report): - print(f"{label:<{max_len}} | \"{header_value}\"") + print(f'{label:<{max_len}} | "{header_value}"') # --------------------------------------------------------------------------- diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py index ef32e04421f7..b4d51f49842c 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py @@ -21,28 +21,11 @@ FAKE_ENDPOINT, FakeCredential, FoundryFeaturesHeaderTestBase, + _NON_BETA_OPTIONAL_TEST_CASES, _RequestCaptured, ) -_NON_BETA_OPTIONAL_TEST_CASES = [ - # Each pytest.param entry has the following positional arguments: - # 1. method_name (str) – "." on AIProjectClient, e.g. "agents.create_version" - # The subclient and method names are parsed automatically from this string. - # 2. expected_header_value (str) – Expected value of the Foundry-Features header when allow_preview=True. - # Use a comma-separated list of feature=version pairs, e.g. "FeatureA=V1Preview,FeatureB=V1Preview". - # The test id is derived automatically from method_name. - pytest.param( - "agents.create_version", - "HostedAgents=V1Preview,WorkflowAgents=V1Preview", - ), - pytest.param( - "evaluation_rules.create_or_update", - "Evaluations=V1Preview", - ), -] - - class CapturingTransport(HttpTransport): """Sync transport that captures the outgoing request and raises _RequestCaptured.""" @@ -94,14 +77,16 @@ def _print_report_optional() -> Iterator[None]: max_len = TestFoundryFeaturesHeaderOptional._report_max_label_len print("\n\nFoundry-Features optional header report (sync) — test_optional_header_present_when_preview_enabled:") for label, header_value in sorted(present_report): - print(f"{label:<{max_len}} | \"{header_value}\"") + print(f'{label:<{max_len}} | "{header_value}"') absent_report = TestFoundryFeaturesHeaderOptional._report_absent if absent_report: max_len = TestFoundryFeaturesHeaderOptional._report_absent_max_label_len - print("\n\nFoundry-Features optional header report (sync) — test_optional_header_absent_when_preview_not_enabled:") + print( + "\n\nFoundry-Features optional header report (sync) — test_optional_header_absent_when_preview_not_enabled:" + ) for label, header_value in sorted(absent_report): - print(f"{label:<{max_len}} | \"{header_value}\"") + print(f'{label:<{max_len}} | "{header_value}"') class TestFoundryFeaturesHeaderOptional(FoundryFeaturesHeaderTestBase): diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py index 06e2c21637f0..711af5221896 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py @@ -22,28 +22,11 @@ FAKE_ENDPOINT, AsyncFakeCredential, FoundryFeaturesHeaderTestBase, + _NON_BETA_OPTIONAL_TEST_CASES, _RequestCaptured, ) -_NON_BETA_OPTIONAL_ASYNC_TEST_CASES = [ - # Each pytest.param entry has the following positional arguments: - # 1. method_name (str) – "." on AIProjectClient, e.g. "agents.create_version" - # The subclient and method names are parsed automatically from this string. - # 2. expected_header_value (str) – Expected value of the Foundry-Features header when allow_preview=True. - # Use a comma-separated list of feature=version pairs, e.g. "FeatureA=V1Preview,FeatureB=V1Preview". - # The test id is derived automatically from method_name. - pytest.param( - "agents.create_version", - "HostedAgents=V1Preview,WorkflowAgents=V1Preview", - ), - pytest.param( - "evaluation_rules.create_or_update", - "Evaluations=V1Preview", - ), -] - - class CapturingAsyncTransport(AsyncHttpTransport): """Async transport that captures the outgoing request and raises _RequestCaptured.""" @@ -91,16 +74,20 @@ def _print_report_optional_async() -> Iterator[None]: present_report = TestFoundryFeaturesHeaderOptionalAsync._report if present_report: max_len = TestFoundryFeaturesHeaderOptionalAsync._report_max_label_len - print("\n\nFoundry-Features optional header report (async) — test_optional_header_present_when_preview_enabled_async:") + print( + "\n\nFoundry-Features optional header report (async) — test_optional_header_present_when_preview_enabled_async:" + ) for label, header_value in sorted(present_report): - print(f"{label:<{max_len}} | \"{header_value}\"") + print(f'{label:<{max_len}} | "{header_value}"') absent_report = TestFoundryFeaturesHeaderOptionalAsync._report_absent if absent_report: max_len = TestFoundryFeaturesHeaderOptionalAsync._report_absent_max_label_len - print("\n\nFoundry-Features optional header report (async) — test_optional_header_absent_when_preview_not_enabled_async:") + print( + "\n\nFoundry-Features optional header report (async) — test_optional_header_absent_when_preview_not_enabled_async:" + ) for label, header_value in sorted(absent_report): - print(f"{label:<{max_len}} | \"{header_value}\"") + print(f'{label:<{max_len}} | "{header_value}"') class TestFoundryFeaturesHeaderOptionalAsync(FoundryFeaturesHeaderTestBase): @@ -144,9 +131,7 @@ async def _assert_header_absent_async(cls, label: str, call: Any) -> None: cls._record_header_absence_assertion(label, request) @pytest.mark.asyncio - @pytest.mark.parametrize( - "method_name,expected_header_value", _NON_BETA_OPTIONAL_ASYNC_TEST_CASES - ) + @pytest.mark.parametrize("method_name,expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) async def test_optional_header_present_when_preview_enabled_async( self, async_client_preview_enabled: AsyncAIProjectClient, @@ -159,9 +144,7 @@ async def test_optional_header_present_when_preview_enabled_async( await self._assert_header_present_async(method_name, self._make_fake_call(method), expected_header_value) @pytest.mark.asyncio - @pytest.mark.parametrize( - "method_name,_expected_header_value", _NON_BETA_OPTIONAL_ASYNC_TEST_CASES - ) + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) async def test_optional_header_absent_when_preview_not_enabled_async( self, async_client_preview_disabled: AsyncAIProjectClient, diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index af3d3e3f30a3..556fb2c307ed 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -30,6 +30,7 @@ from azure.ai.projects.models._models import AgentDetails, AgentVersionDetails from azure.ai.projects import AIProjectClient from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient +from azure.core.credentials import AzureKeyCredential # Store reference to built-in open before any mocking occurs _BUILTIN_OPEN = open @@ -40,6 +41,7 @@ EnvironmentVariableLoader, "", foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", + foundry_project_api_key="sanitized-api-key", foundry_model_name="sanitized-model-deployment-name", image_generation_model_deployment_name="sanitized-gpt-image", bing_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-bing-connection", @@ -292,10 +294,14 @@ def open_with_lf( return patched_open_crlf_to_lf(file, mode, buffering, encoding, errors, newline, closefd, opener) # helper function: create projects client using environment variables - def create_client(self, *, allow_preview: bool = False, **kwargs) -> AIProjectClient: + def create_client(self, *, allow_preview: bool = False, use_api_key: bool = False, **kwargs) -> AIProjectClient: # fetch environment variables endpoint = kwargs.pop("foundry_project_endpoint") - credential = self.get_credential(AIProjectClient, is_async=False) + if use_api_key: + api_key = kwargs.pop("foundry_project_api_key") + credential = AzureKeyCredential(api_key) + else: + credential = self.get_credential(AIProjectClient, is_async=False) print(f"Creating AIProjectClient with endpoint: {endpoint}") @@ -309,10 +315,16 @@ def create_client(self, *, allow_preview: bool = False, **kwargs) -> AIProjectCl return client # helper function: create async projects client using environment variables - def create_async_client(self, *, allow_preview: bool = False, **kwargs) -> AsyncAIProjectClient: + def create_async_client( + self, *, allow_preview: bool = False, use_api_key: bool = False, **kwargs + ) -> AsyncAIProjectClient: # fetch environment variables endpoint = kwargs.pop("foundry_project_endpoint") - credential = self.get_credential(AsyncAIProjectClient, is_async=True) + if use_api_key: + api_key = kwargs.pop("foundry_project_api_key") + credential = AzureKeyCredential(api_key) + else: + credential = self.get_credential(AsyncAIProjectClient, is_async=True) print(f"Creating AsyncAIProjectClient with endpoint: {endpoint}") From 98b8f735609ba766ef4e5f824b51a7906b5ac235 Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Wed, 18 Mar 2026 13:32:46 -0700 Subject: [PATCH 19/36] New samples (#45775) * New samples * resolved comments --- sdk/ai/azure-ai-projects/CHANGELOG.md | 3 + ..._workflow_multi_agent_with_mcp_approval.py | 250 ++++++++++++++++++ ...gent_code_interpreter_structured_inputs.py | 112 ++++++++ ...ple_agent_file_search_structured_inputs.py | 110 ++++++++ .../tests/samples/test_samples.py | 7 +- 5 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_with_mcp_approval.py create mode 100644 sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_structured_inputs.py create mode 100644 sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_structured_inputs.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 2fda719939ac..0d607082b57e 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -19,6 +19,9 @@ * Renamed environment variable `AZURE_AI_PROJECT_ENDPOINT` to `FOUNDRY_PROJECT_ENDPOINT` in all samples. * Renamed environment variable `AZURE_AI_MODEL_DEPLOYMENT_NAME` to `FOUNDRY_MODEL_NAME` in all samples. * Renamed environment variable `AZURE_AI_MODEL_AGENT_NAME` to `FOUNDRY_AGENT_NAME` in all samples. +* Added structured inputs + file upload sample (`sample_agent_structured_inputs_file_upload.py`) demonstrating passing an uploaded file ID to an agent at runtime. +* Added structured inputs + File Search sample (`sample_agent_file_search_structured_inputs.py`) demonstrating configuring File Search tool resources via structured inputs. +* Added structured inputs + Code Interpreter sample (`sample_agent_code_interpreter_structured_inputs.py`) demonstrating passing an uploaded file ID to Code Interpreter via structured inputs. * Added CSV evaluation sample (`sample_evaluations_builtin_with_csv.py`) demonstrating evaluation with an uploaded CSV dataset. * Added synthetic data evaluation samples (`sample_synthetic_data_agent_evaluation.py`) and (`sample_synthetic_data_model_evaluation.py`). diff --git a/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_with_mcp_approval.py b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_with_mcp_approval.py new file mode 100644 index 000000000000..2ef0109250c5 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/sample_workflow_multi_agent_with_mcp_approval.py @@ -0,0 +1,250 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to create a multi-agent workflow using a synchronous client + with a student agent (using MCP tools) answering questions and then a teacher + agent checking the answer. The workflow handles MCP approval requests as always. + + The student agent can access external resources via MCP tools if needed to answer + questions that require additional context or information. + +USAGE: + python sample_workflow_multi_agent_with_mcp_approval.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +from dotenv import load_dotenv +from openai.types.responses.response_input_param import McpApprovalResponse, ResponseInputParam + +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import ( + PromptAgentDefinition, + WorkflowAgentDefinition, + MCPTool, +) + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential, allow_preview=True) as project_client, + project_client.get_openai_client() as openai_client, +): + # Define MCP tool for accessing external resources (optional - can be removed for simpler demo) + # Note: MCP tools in workflows may require special handling depending on the use case + mcp_tool = MCPTool( + server_label="api-specs", + server_url="https://gitmcp.io/Azure/azure-rest-api-specs", + require_approval="always", + ) + + # Create Teacher Agent + teacher_agent = project_client.agents.create_version( + agent_name="teacher-agent", + definition=PromptAgentDefinition( + model=os.environ["FOUNDRY_MODEL_NAME"], + instructions="""You are a teacher that create Foundry project question for student and check answer. + Verify student's answer from mcp tools. + If the answer is correct, you stop the conversation by saying [COMPLETE]. + If the answer is wrong, you ask student to fix it.""", + tools=[mcp_tool], + ), + ) + print(f"Agent created (id: {teacher_agent.id}, name: {teacher_agent.name}, version: {teacher_agent.version})") + + # Create Student Agent WITHOUT MCP tool initially to keep the sample simple + # To demonstrate MCP approval in workflows, the tool would need to be triggered by appropriate queries + student_agent = project_client.agents.create_version( + agent_name="student-agent", + definition=PromptAgentDefinition( + model=os.environ["FOUNDRY_MODEL_NAME"], + instructions="""You are a student who answers questions from the teacher. + When the teacher gives you a question, you answer it using mcp tool.""", + tools=[mcp_tool], + ), + ) + print(f"Agent created (id: {student_agent.id}, name: {student_agent.name}, version: {student_agent.version})") + + # Create Multi-Agent Workflow + workflow_yaml = f""" +kind: workflow +trigger: + kind: OnConversationStart + id: my_workflow + actions: + - kind: SetVariable + id: set_variable_input_task + variable: Local.LatestMessage + value: "=UserMessage(System.LastMessageText)" + + - kind: CreateConversation + id: create_student_conversation + conversationId: Local.StudentConversationId + + - kind: CreateConversation + id: create_teacher_conversation + conversationId: Local.TeacherConversationId + + - kind: InvokeAzureAgent + id: student_agent + description: The student node + conversationId: "=Local.StudentConversationId" + agent: + name: {student_agent.name} + input: + messages: "=Local.LatestMessage" + output: + messages: Local.LatestMessage + + - kind: InvokeAzureAgent + id: teacher_agent + description: The teacher node + conversationId: "=Local.TeacherConversationId" + agent: + name: {teacher_agent.name} + input: + messages: "=Local.LatestMessage" + output: + messages: Local.LatestMessage + + - kind: SendActivity + id: send_teacher_reply + activity: "{{Last(Local.LatestMessage).Text}}" + + - kind: SetVariable + id: set_variable_turncount + variable: Local.TurnCount + value: "=Local.TurnCount + 1" + + - kind: ConditionGroup + id: completion_check + conditions: + - condition: '=!IsBlank(Find("[COMPLETE]", Upper(Last(Local.LatestMessage).Text)))' + id: check_done + actions: + - kind: EndConversation + id: end_workflow + + - condition: "=Local.TurnCount >= 4" + id: check_turn_count_exceeded + actions: + - kind: SendActivity + id: send_activity_tired + activity: "Let's try again later...I am tired." + + elseActions: + - kind: GotoAction + id: goto_student_agent + actionId: student_agent +""" + + workflow = project_client.agents.create_version( + agent_name="student-teacher-workflow", + definition=WorkflowAgentDefinition(workflow=workflow_yaml), + ) + + print(f"Agent created (id: {workflow.id}, name: {workflow.name}, version: {workflow.version})") + + conversation = openai_client.conversations.create() + print(f"Created conversation (id: {conversation.id})") + + stream = openai_client.responses.create( + conversation=conversation.id, + extra_body={"agent_reference": {"name": workflow.name, "type": "agent_reference"}}, + input="Please summarize the Azure REST API specifications Readme", + stream=True, + ) + + # Track MCP approval requests during streaming + mcp_approval_requests = [] + response = None + + for event in stream: + print(f"Event {event.sequence_number} type '{event.type}'", end="") + if ( + event.type in ("response.output_item.added", "response.output_item.done") + ) and event.item.type == "workflow_action": # pyright: ignore [reportAttributeAccessIssue] + print( + f": item action ID '{event.item.action_id}' is '{event.item.status}' (previous action ID: '{event.item.previous_action_id}')", # pyright: ignore [reportAttributeAccessIssue] + end="", + ) + elif ( + event.type == "response.output_item.done" and event.item.type == "mcp_approval_request" + ): # pyright: ignore [reportAttributeAccessIssue] + # Collect MCP approval requests during streaming + print( + f": MCP approval request for server '{event.item.server_label}'", end="" + ) # pyright: ignore [reportAttributeAccessIssue] + mcp_approval_requests.append(event.item) # pyright: ignore [reportAttributeAccessIssue] + elif event.type == "response.output_item.added" and hasattr( + event, "item" + ): # pyright: ignore [reportAttributeAccessIssue] + # Print other item types + print(f": item type '{event.item.type}'", end="") # pyright: ignore [reportAttributeAccessIssue] + elif event.type == "response.completed": + response = openai_client.responses.retrieve(event.response.id) + print(f": Final Response: {response.output_text}", end="") + elif event.type == "response.failed": + response = openai_client.responses.retrieve(event.response.id) + print(f": Response failed - Error: {response.error}", end="") + print("", flush=True) + + print(f"\nStream completed. Response status: {response.status if response else 'No response'}") + print(f"MCP approval requests collected: {len(mcp_approval_requests)}") + + # Process any MCP approval requests that were collected + if mcp_approval_requests and response: + print(f"\nProcessing {len(mcp_approval_requests)} MCP approval request(s)...") + input_list: ResponseInputParam = [] + + for item in mcp_approval_requests: + if item.server_label == "api-specs" and item.id: + # Automatically approve the MCP request to allow the agent to proceed + print(f"Approving MCP request for server '{item.server_label}'") + input_list.append( + McpApprovalResponse( + type="mcp_approval_response", + approve=True, + approval_request_id=item.id, + ) + ) + + if input_list: + # Send the approval response back to continue the agent's work + print("\nContinuing workflow with MCP approvals...") + response = openai_client.responses.create( + input=input_list, + previous_response_id=response.id, + extra_body={"agent_reference": {"name": workflow.name, "type": "agent_reference"}}, + ) + print(f"Agent response after approval: {response.output_text}") + + openai_client.conversations.delete(conversation_id=conversation.id) + print("Conversation deleted") + + project_client.agents.delete_version(agent_name=workflow.name, agent_version=workflow.version) + print("Workflow deleted") + + project_client.agents.delete_version(agent_name=student_agent.name, agent_version=student_agent.version) + print("Student Agent deleted") + + project_client.agents.delete_version(agent_name=teacher_agent.name, agent_version=teacher_agent.version) + print("Teacher Agent deleted") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_structured_inputs.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_structured_inputs.py new file mode 100644 index 000000000000..15921ab7fd2c --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_code_interpreter_structured_inputs.py @@ -0,0 +1,112 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to run Prompt Agent operations + using the Code Interpreter Tool and a synchronous client. + + It is intentionally very similar to `sample_agent_code_interpreter.py`, but shows + how to use structured inputs to pass an uploaded file id at runtime. + + The key idea is that structured input acts as a placeholder and is later bound to actual data in the response call. + + +USAGE: + python sample_agent_code_interpreter_structured_inputs.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os +from io import BytesIO +from typing import Any, cast + +from dotenv import load_dotenv + +from azure.ai.projects.models._models import AutoCodeInterpreterToolParam +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, CodeInterpreterTool, StructuredInputDefinition + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as openai_client, +): + + # Upload a tiny CSV so the code interpreter has a file to work with. + csv_bytes = b"x\n1\n2\n3\n" + csv_file = BytesIO(csv_bytes) + csv_file.name = "numbers.csv" # type: ignore[attr-defined] + uploaded = openai_client.files.create(purpose="assistants", file=csv_file) + print(f"File uploaded (id: {uploaded.id})") + + tool = CodeInterpreterTool(container=AutoCodeInterpreterToolParam(file_ids=["{{analysis_file_id}}"])) + + agent_definition = PromptAgentDefinition( + model=os.environ["FOUNDRY_MODEL_NAME"], + instructions="You are a helpful assistant.", + tools=[tool], + structured_inputs={ + "analysis_file_id": StructuredInputDefinition( + description="File id available to the code interpreter", + required=True, + schema={"type": "string"}, + ), + }, + ) + + # Create agent with code interpreter tool + agent = project_client.agents.create_version( + agent_name="MyAgent", + definition=agent_definition, + description="Code interpreter agent for data analysis and visualization.", + ) + print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + # Create a conversation for the agent interaction + conversation = openai_client.conversations.create() + print(f"Created conversation (id: {conversation.id})") + + # Send request for the agent to generate a multiplication chart. + response = openai_client.responses.create( + conversation=conversation.id, + input=( + "Could you please generate a multiplication chart showing the products for 1-10 multiplied by 1-10 " + "(a 10x10 times table)? Also, using the code interpreter, read numbers.csv and return the sum of x." + ), + extra_body={ + "agent_reference": {"name": agent.name, "type": "agent_reference"}, + "structured_inputs": {"analysis_file_id": uploaded.id}, + }, + tool_choice="required", + ) + print(f"Response completed (id: {response.id})") + + # Print code executed by the code interpreter tool. + code = next((output.code for output in response.output if output.type == "code_interpreter_call"), "") + print("Code Interpreter code:") + print(code) + + # Print final assistant text output. + print(f"Agent response: {response.output_text}") + + print("\nCleaning up...") + project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") diff --git a/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_structured_inputs.py b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_structured_inputs.py new file mode 100644 index 000000000000..f9af76a1c920 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/agents/tools/sample_agent_file_search_structured_inputs.py @@ -0,0 +1,110 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to run Prompt Agent operations using the File Search Tool. + + It is intentionally very similar to `sample_agent_file_search.py`, but shows + how to use structured inputs to pass an uploaded file id at runtime. + + The key idea is that structured input acts as a placeholder and is later bound to actual data in the response call. + +USAGE: + python sample_agent_file_search_structured_inputs.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT (preferred) or AZURE_AI_PROJECT_ENDPOINT - The Azure AI Project endpoint. + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model. + +NOTES: + This sample is intentionally kept very close to `sample_agent_file_search.py`. + It does not include fallback logic. +""" + +import os +from dotenv import load_dotenv + +from azure.identity import DefaultAzureCredential + +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import PromptAgentDefinition, FileSearchTool, StructuredInputDefinition + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as openai_client, +): + # Create vector store for file search + vector_store = openai_client.vector_stores.create(name="ProductInfoStore") + print(f"Vector store created (id: {vector_store.id})") + + # Load the file to be indexed for search + asset_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../assets/product_info.md")) + + # Upload file to vector store + with open(asset_file_path, "rb") as f: + file = openai_client.vector_stores.files.upload_and_poll(vector_store_id=vector_store.id, file=f) + print(f"File uploaded to vector store (id: {file.id})") + + # Tool resources are templated and resolved at runtime via structured inputs. + tool = FileSearchTool(vector_store_ids=["{{vector_store_id}}"]) + + agent_definition = PromptAgentDefinition( + model=os.environ["FOUNDRY_MODEL_NAME"], + instructions=( + "You are a helpful assistant that can search through product information. " + "The indexed source file id is {{vector_store_file_id}}." + ), + tools=[tool], + structured_inputs={ + "vector_store_id": StructuredInputDefinition( + description="Vector store id used by the file_search tool", + required=True, + schema={"type": "string"}, + ), + "vector_store_file_id": StructuredInputDefinition( + description="File id uploaded into the vector store", + required=True, + schema={"type": "string"}, + ), + }, + ) + + # Create agent with file search tool + agent = project_client.agents.create_version( + agent_name="MyAgent", + definition=agent_definition, + description="File search agent for product information queries.", + ) + + print(f"Agent created (id: {agent.id}, name: {agent.name}, version: {agent.version})") + + # Create a conversation for the agent interaction + conversation = openai_client.conversations.create() + print(f"Created conversation (id: {conversation.id})") + + # Send a query to search through the uploaded file + response = openai_client.responses.create( + conversation=conversation.id, + input="Tell me about Contoso products", + extra_body={ + "agent_reference": {"name": agent.name, "type": "agent_reference"}, + "structured_inputs": {"vector_store_id": vector_store.id, "vector_store_file_id": file.id}, + }, + ) + print(f"Agent response: {response.output_text}") + + print("\nCleaning up...") + project_client.agents.delete_version(agent_name=agent.name, agent_version=agent.version) + print("Agent deleted") diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 626b9931c279..4362f60f188d 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -46,6 +46,8 @@ class TestSamples(AzureRecordedTestCase): get_sample_paths( "agents/tools", samples_to_skip=[ + "sample_agent_file_search_structured_inputs.py", # No issue to run. Just posepone recording. + "sample_agent_code_interpreter_structured_inputs.py", # No issue to run. Just posepone recording. "sample_agent_azure_function.py", # In the list of additional sample tests above due to more parameters needed "sample_agent_computer_use.py", # 400 BadRequestError: Invalid URI (URI string too long) "sample_agent_browser_automation.py", # APITimeoutError: request timed out @@ -96,8 +98,9 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: get_sample_paths( "agents", samples_to_skip=[ - "sample_workflow_multi_agent.py" - ], # I see in sample spew: "Event 10 type 'response.failed'" with error message in payload "The specified agent was not found. Please verify that the agent name and version are correct". + "sample_workflow_multi_agent.py", # No issue to run. Just posepone recording. + "sample_workflow_multi_agent_with_mcp_approval.py", # No issue to run. Just posepone recording. + ], ), ) @servicePreparer() From ed5cba52030bd987ce3e7ad9d36f6980d8e9deb0 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Thu, 19 Mar 2026 11:23:15 -0700 Subject: [PATCH 20/36] Fix missing IntelliSense from returned OpenAI client (#45800) --- sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py | 2 +- sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 9d5ca36cce62..51db937048cf 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -105,7 +105,7 @@ def __init__( self.telemetry = TelemetryOperations(self) # type: ignore @distributed_trace - def get_openai_client(self, **kwargs: Any) -> "OpenAI": # type: ignore[name-defined] # pylint: disable=too-many-statements + def get_openai_client(self, **kwargs: Any) -> OpenAI: """Get an authenticated OpenAI client from the `openai` package. Keyword arguments are passed to the OpenAI client constructor. 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 7172df9225b7..b5bce67b4d85 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,7 +105,7 @@ def __init__( self.telemetry = TelemetryOperations(self) # type: ignore @distributed_trace - def get_openai_client(self, **kwargs: Any) -> "AsyncOpenAI": # type: ignore[name-defined] # pylint: disable=too-many-statements + def get_openai_client(self, **kwargs: Any) -> AsyncOpenAI: """Get an authenticated AsyncOpenAI client from the `openai` package. Keyword arguments are passed to the AsyncOpenAI client constructor. From 3b48acd0e18210fc87bb022df7ba9e5235f90d22 Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Fri, 20 Mar 2026 10:44:27 -0700 Subject: [PATCH 21/36] LLM validation use 5.2 and chat completion (#45816) * LLM validation use 5.2 and chat completion * change log * Resolved comments --- sdk/ai/azure-ai-projects/CHANGELOG.md | 1 + sdk/ai/azure-ai-projects/assets.json | 2 +- .../sample_chat_completions_basic.py | 71 +++++++++++ .../sample_chat_completions_basic_async.py | 77 ++++++++++++ .../tests/samples/sample_executor.py | 113 +++++++++--------- .../tests/samples/test_samples.py | 35 +++--- .../tests/samples/test_samples_async.py | 33 ++--- .../tests/samples/test_samples_evaluations.py | 6 - .../tests/samples/test_samples_helpers.py | 20 ++++ sdk/ai/azure-ai-projects/tests/test_base.py | 2 + 10 files changed, 265 insertions(+), 95 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic.py create mode 100644 sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic_async.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 0d607082b57e..6943923ff0fb 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -24,6 +24,7 @@ * Added structured inputs + Code Interpreter sample (`sample_agent_code_interpreter_structured_inputs.py`) demonstrating passing an uploaded file ID to Code Interpreter via structured inputs. * Added CSV evaluation sample (`sample_evaluations_builtin_with_csv.py`) demonstrating evaluation with an uploaded CSV dataset. * Added synthetic data evaluation samples (`sample_synthetic_data_agent_evaluation.py`) and (`sample_synthetic_data_model_evaluation.py`). +* Added Chat Completions basic samples (`sample_chat_completions_basic.py`, `sample_chat_completions_basic_async.py`) demonstrating chat completions calls using `AIProjectClient` + the OpenAI-compatible client. ### Other Changes diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index acad4b35c8fe..05c02f4d58b3 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_20150d1e9d" + "Tag": "python/ai/azure-ai-projects_23b51dc081" } diff --git a/sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic.py b/sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic.py new file mode 100644 index 000000000000..78d07ed5540e --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic.py @@ -0,0 +1,71 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to run a basic chat completions operation + using the synchronous AIProjectClient and OpenAI clients. + + See also https://platform.openai.com/docs/api-reference/chat/create?lang=python + +USAGE: + python sample_chat_completions_basic.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import os + +from dotenv import load_dotenv + +from azure.ai.projects import AIProjectClient +from azure.identity import DefaultAzureCredential +from openai.types.chat import ChatCompletionMessageParam + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model = os.environ["FOUNDRY_MODEL_NAME"] + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, +): + # [START chat_completions] + with project_client.get_openai_client() as openai_client: + messages: list[ChatCompletionMessageParam] = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Give me one fun fact about the Eiffel Tower."}, + ] + + completion = openai_client.chat.completions.create( + model=model, + messages=messages, + temperature=0, + ) + # [END chat_completions] + + assistant_message = completion.choices[0].message.content + print(f"Assistant: {assistant_message}") + + messages.append({"role": "assistant", "content": assistant_message}) + messages.append({"role": "user", "content": "Now give me one related fun fact."}) + + completion = openai_client.chat.completions.create( + model=model, + messages=messages, + temperature=0, + ) + + assistant_message = completion.choices[0].message.content + print(f"Assistant: {assistant_message}") diff --git a/sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic_async.py b/sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic_async.py new file mode 100644 index 000000000000..d0c329eb899d --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/chat_completions/sample_chat_completions_basic_async.py @@ -0,0 +1,77 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to run a basic chat completions operation + using the asynchronous AIProjectClient and AsyncOpenAI clients. + + See also https://platform.openai.com/docs/api-reference/chat/create?lang=python + +USAGE: + python sample_chat_completions_basic_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0" python-dotenv aiohttp + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. + 2) FOUNDRY_MODEL_NAME - The deployment name of the AI model, as found under the "Name" column in + the "Models + endpoints" tab in your Microsoft Foundry project. +""" + +import asyncio +import os + +from dotenv import load_dotenv + +from azure.ai.projects.aio import AIProjectClient +from azure.identity.aio import DefaultAzureCredential +from openai.types.chat import ChatCompletionMessageParam + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model = os.environ["FOUNDRY_MODEL_NAME"] + + +async def main() -> None: + + async with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as openai_client, + ): + messages: list[ChatCompletionMessageParam] = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Give me one fun fact about the Eiffel Tower."}, + ] + + completion = await openai_client.chat.completions.create( + model=model, + messages=messages, + temperature=0, + ) + + assistant_message = completion.choices[0].message.content + print(f"Assistant: {assistant_message}") + + messages.append({"role": "assistant", "content": assistant_message}) + messages.append({"role": "user", "content": "Now give me one related fun fact."}) + + completion = await openai_client.chat.completions.create( + model=model, + messages=messages, + temperature=0, + ) + + assistant_message = completion.choices[0].message.content + print(f"Assistant: {assistant_message}") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/ai/azure-ai-projects/tests/samples/sample_executor.py b/sdk/ai/azure-ai-projects/tests/samples/sample_executor.py index ed9d98a002d7..a2e939729b8e 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/sample_executor.py +++ b/sdk/ai/azure-ai-projects/tests/samples/sample_executor.py @@ -43,6 +43,11 @@ from pytest import MonkeyPatch from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient +PLAYBACK_LLM_VALIDATION_PROJECT_ENDPOINT = ( + "https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name" +) +PLAYBACK_LLM_VALIDATION_MODEL = "sanitized-model-deployment-name" + @overload def get_sample_paths(sub_folder: str, *, samples_to_test: list[str]) -> list: @@ -680,19 +685,16 @@ def execute(self, patched_open_fn=None): print(f"\nSample execution failed! Print statements logged to: {log_file}") raise - def validate_print_calls_by_llm( - self, - *, - instructions: str, - project_endpoint: str, - model: str = "gpt-4o", - ): + def validate_print_calls_by_llm(self, *, instructions: str): """Validate captured print output using synchronous OpenAI client.""" if not instructions or not instructions.strip(): raise ValueError("instructions must be a non-empty string") - if not project_endpoint: - raise ValueError("project_endpoint must be provided") - endpoint = project_endpoint + if is_live(): + endpoint = os.environ["LLM_VALIDATION_PROJECT_ENDPOINT"] + model = "gpt-5.2" + else: + endpoint = PLAYBACK_LLM_VALIDATION_PROJECT_ENDPOINT + model = PLAYBACK_LLM_VALIDATION_MODEL print(f"For validating console output, creating AIProjectClient with endpoint: {endpoint}") assert isinstance(self.tokenCredential, TokenCredential) or isinstance( self.tokenCredential, FakeTokenCredential @@ -887,16 +889,16 @@ async def validate_print_calls_by_llm_async( self, *, instructions: str, - project_endpoint: str, - model: str = "gpt-4o", - use_code_interpreter: bool = True, ): """Validate captured print output using asynchronous OpenAI client.""" if not instructions or not instructions.strip(): raise ValueError("instructions must be a non-empty string") - if not project_endpoint: - raise ValueError("project_endpoint must be provided") - endpoint = project_endpoint + if is_live(): + endpoint = os.environ["LLM_VALIDATION_PROJECT_ENDPOINT"] + model = "gpt-5.2" + else: + endpoint = PLAYBACK_LLM_VALIDATION_PROJECT_ENDPOINT + model = PLAYBACK_LLM_VALIDATION_MODEL print(f"For validating console output, creating AIProjectClient with endpoint: {endpoint}") assert isinstance(self.tokenCredential, AsyncTokenCredential) or isinstance( self.tokenCredential, AsyncFakeCredential @@ -945,51 +947,46 @@ async def _upload_file_for_validation_async(file_obj, placeholder_value: str) -> model=model, ) - if use_code_interpreter: - sample_code_files = self._collect_sample_code_files_for_code_interpreter() - code_interpreter_file_ids: list[str] - - if is_live(): - uploaded_code_interpreter_file_ids: list[str] = [] - # Keep upload/delete out of recordings because multipart payloads contain - # full file bytes and become brittle whenever sample files change. - # - # Since upload calls are skipped from recording, playback cannot discover - # runtime file IDs via `files.create`. To keep `responses.create` replayable, - # sanitize each live file ID to a deterministic placeholder and reuse the - # same placeholders in playback mode below. - for file_path in sample_code_files: - with open(file_path, "rb") as source_file: - scrubbed_file_id = ( - f"code_interpreter_file_{len(uploaded_code_interpreter_file_ids) + 1}" - ) - uploaded_file_id = await _upload_file_for_validation_async( - source_file, scrubbed_file_id - ) - uploaded_code_interpreter_file_ids.append(uploaded_file_id) - - code_interpreter_file_ids = [validation_file_id, *uploaded_code_interpreter_file_ids] - else: - # Playback counterpart of the live sanitization mapping above. - # Must match the same deterministic sequence used during recording. - code_interpreter_file_ids = [ - "validation_log_file_1", - *[f"code_interpreter_file_{index}" for index, _ in enumerate(sample_code_files, start=1)], - ] - - code_interpreter_container: dict[str, object] = { - "type": "auto", - } - if code_interpreter_file_ids: - code_interpreter_container["file_ids"] = code_interpreter_file_ids + sample_code_files = self._collect_sample_code_files_for_code_interpreter() + code_interpreter_file_ids: list[str] - request_params["tools"] = [ - { - "type": "code_interpreter", - "container": code_interpreter_container, - } + if is_live(): + uploaded_code_interpreter_file_ids: list[str] = [] + # Keep upload/delete out of recordings because multipart payloads contain + # full file bytes and become brittle whenever sample files change. + # + # Since upload calls are skipped from recording, playback cannot discover + # runtime file IDs via `files.create`. To keep `responses.create` replayable, + # sanitize each live file ID to a deterministic placeholder and reuse the + # same placeholders in playback mode below. + for file_path in sample_code_files: + with open(file_path, "rb") as source_file: + scrubbed_file_id = f"code_interpreter_file_{len(uploaded_code_interpreter_file_ids) + 1}" + uploaded_file_id = await _upload_file_for_validation_async(source_file, scrubbed_file_id) + uploaded_code_interpreter_file_ids.append(uploaded_file_id) + + code_interpreter_file_ids = [validation_file_id, *uploaded_code_interpreter_file_ids] + else: + # Playback counterpart of the live sanitization mapping above. + # Must match the same deterministic sequence used during recording. + code_interpreter_file_ids = [ + "validation_log_file_1", + *[f"code_interpreter_file_{index}" for index, _ in enumerate(sample_code_files, start=1)], ] + code_interpreter_container: dict[str, object] = { + "type": "auto", + } + if code_interpreter_file_ids: + code_interpreter_container["file_ids"] = code_interpreter_file_ids + + request_params["tools"] = [ + { + "type": "code_interpreter", + "container": code_interpreter_container, + } + ] + response = await openai_client.responses.create(**request_params) test_report = json.loads(response.output_text) except Exception as e: # pylint: disable=broad-exception-caught diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py index 4362f60f188d..1f92d412a7af 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples.py @@ -16,6 +16,7 @@ from test_samples_helpers import ( agent_tools_instructions, agents_instructions, + chat_completions_instructions, memories_instructions, resource_management_instructions, get_sample_env_vars, @@ -64,8 +65,6 @@ def test_agent_tools_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -89,8 +88,6 @@ def test_memory_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=memories_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -112,8 +109,6 @@ def test_agents_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=agents_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -134,8 +129,6 @@ def test_connections_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -154,8 +147,6 @@ def test_files_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -174,8 +165,6 @@ def test_deployments_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -194,8 +183,24 @@ def test_datasets_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], + ) + + @pytest.mark.parametrize( + "sample_path", + get_sample_paths( + "chat_completions", + samples_to_skip=[], + ), + ) + @servicePreparer() + @SamplePathPasser() + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_chat_completions_samples(self, sample_path: str, **kwargs) -> None: + env_vars = get_sample_env_vars(kwargs) + executor = SyncSampleExecutor(self, sample_path, env_vars=env_vars, **kwargs) + executor.execute() + executor.validate_print_calls_by_llm( + instructions=chat_completions_instructions, ) @pytest.mark.parametrize( @@ -217,6 +222,4 @@ def test_finetuning_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=fine_tuning_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py index ab25f7e1b48a..7deabdcf75ca 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_async.py @@ -17,6 +17,7 @@ get_sample_env_vars, memories_instructions, agents_instructions, + chat_completions_instructions, resource_management_instructions, ) @@ -50,8 +51,6 @@ async def test_agent_tools_samples_async(self, sample_path: str, **kwargs) -> No await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agent_tools_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -75,8 +74,6 @@ async def test_memory_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=memories_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -95,8 +92,6 @@ async def test_agents_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=agents_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -117,8 +112,6 @@ async def test_connections_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -139,8 +132,6 @@ async def test_files_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -159,8 +150,6 @@ async def test_deployments_samples(self, sample_path: str, **kwargs) -> None: await executor.execute_async() await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) @pytest.mark.parametrize( @@ -184,6 +173,22 @@ async def test_datasets_samples(self, sample_path: str, **kwargs) -> None: # Proxy server probably not able to parse the captured print content await executor.validate_print_calls_by_llm_async( instructions=resource_management_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) + + @pytest.mark.parametrize( + "sample_path", + get_async_sample_paths( + "chat_completions", + samples_to_skip=[], + ), + ) + @servicePreparer() + @SamplePathPasser() + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_chat_completions_samples(self, sample_path: str, **kwargs) -> None: + env_vars = get_sample_env_vars(kwargs) + executor = AsyncSampleExecutor(self, sample_path, env_vars=env_vars, **kwargs) + await executor.execute_async() + await executor.validate_print_calls_by_llm_async( + instructions=chat_completions_instructions, + ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index 14b0f827d0a4..df5c91e08d91 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -188,8 +188,6 @@ def test_evaluation_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) # To run this test with a specific sample, use: @@ -220,8 +218,6 @@ def test_agentic_evaluator_samples(self, sample_path: str, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) # To run this test, use: @@ -251,6 +247,4 @@ def test_generic_agentic_evaluator_sample(self, **kwargs) -> None: executor.execute() executor.validate_print_calls_by_llm( instructions=evaluations_instructions, - project_endpoint=kwargs["foundry_project_endpoint"], - model=kwargs["foundry_model_name"], ) diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_helpers.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_helpers.py index f6ff0b12b9e5..81b13079761f 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_helpers.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_helpers.py @@ -91,6 +91,26 @@ Always include `reason` with a concise explanation tied to the observed print output. """.strip() +chat_completions_instructions = """ +We just ran Python code and captured print/log output in an attached log file (TXT). +Validate whether sample execution/output is correct for Chat Completions scenarios. + +Successful output typically shows one or more of: +- A user prompt and a corresponding assistant response. +- Reasonable, on-topic completion content. + +Mark `correct = false` for: +- Exceptions, stack traces, explicit error/failure messages. +- Timeout/auth/connection/service errors that prevent normal completion. +- Malformed/corrupted output indicating broken processing. +- Output that clearly does not answer the prompt in the sample. + +Mark `correct = true` when execution succeeds and the assistant output is coherent and +responds to the printed prompt, even if the exact wording varies. + +Always include `reason` with a concise explanation tied to the observed print output. +""".strip() + resource_management_instructions = """ We just ran Python code and captured print/log output in an attached log file (TXT). Validate whether sample execution/output is correct for resource-management samples (for example diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index 556fb2c307ed..28fabbc6308a 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -43,6 +43,7 @@ foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", foundry_project_api_key="sanitized-api-key", foundry_model_name="sanitized-model-deployment-name", + llm_validation_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", image_generation_model_deployment_name="sanitized-gpt-image", bing_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-bing-connection", ai_search_project_connection_id="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/sanitized-resource-group/providers/Microsoft.CognitiveServices/accounts/sanitized-account/projects/sanitized-project/connections/sanitized-ai-search-connection", @@ -77,6 +78,7 @@ "", foundry_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", foundry_model_name="sanitized-model-deployment-name", + llm_validation_project_endpoint="https://sanitized-account-name.services.ai.azure.com/api/projects/sanitized-project-name", azure_ai_projects_azure_subscription_id="00000000-0000-0000-0000-000000000000", azure_ai_projects_azure_resource_group="sanitized-resource-group", azure_ai_projects_azure_aoai_account="sanitized-aoai-account", From aa8f2139f0c95d86c76c5912fd77ec0f82ffe908 Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Fri, 20 Mar 2026 10:45:37 -0700 Subject: [PATCH 22/36] update .env.template --- sdk/ai/azure-ai-projects/.env.template | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index 6ffe61b712d7..5172fb59eb38 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -66,6 +66,9 @@ PAUSED_FINE_TUNING_JOB_ID= AZURE_SUBSCRIPTION_ID= AZURE_RESOURCE_GROUP= +# Used in all samples +LLM_VALIDATION_PROJECT_ENDPOINT= + # Used in Fine-tuning samples AZURE_AI_PROJECTS_AZURE_SUBSCRIPTION_ID= AZURE_AI_PROJECTS_AZURE_RESOURCE_GROUP= From e6c5933419fdcc3b4585f9cd37a110b20d72b08e Mon Sep 17 00:00:00 2001 From: M-Hietala <78813398+M-Hietala@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:01:40 -0500 Subject: [PATCH 23/36] making trace context propagation enabled by default (#45830) --- sdk/ai/azure-ai-projects/CHANGELOG.md | 2 +- sdk/ai/azure-ai-projects/README.md | 18 +- .../telemetry/_ai_project_instrumentor.py | 21 ++- .../telemetry/test_ai_agents_instrumentor.py | 169 ++++++++++++++++++ 4 files changed, 196 insertions(+), 14 deletions(-) diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 6943923ff0fb..32b494ab610b 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -8,7 +8,7 @@ ### Breaking Changes -* Placeholder +* Tracing: trace context propagation is enabled by default when tracing is enabled. ### Bugs Fixed diff --git a/sdk/ai/azure-ai-projects/README.md b/sdk/ai/azure-ai-projects/README.md index 8bb065bc3893..6fdff855ad7a 100644 --- a/sdk/ai/azure-ai-projects/README.md +++ b/sdk/ai/azure-ai-projects/README.md @@ -1218,17 +1218,15 @@ Trace context propagation allows client-side spans generated by the Projects SDK This feature ensures that all operations within a distributed trace share the same trace ID, providing end-to-end visibility across your application and Azure services in your observability backend (such as Azure Monitor). -To enable trace context propagation, set the `AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION` environment variable to `true`: +Trace context propagation is **enabled by default** when tracing is enabled (for example through `configure_azure_monitor` or the `AIProjectInstrumentor().instrument()` call). To disable it, set the `AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION` environment variable to `false`, or pass `enable_trace_context_propagation=False` to the `AIProjectInstrumentor().instrument()` call. -If no value is provided for the `enable_trace_context_propagation` parameter with the AIProjectInstrumentor.instrument()` call and the environment variable is not set, trace context propagation defaults to `false` (opt-in). +**When does the change take effect?** +- Changes to `enable_trace_context_propagation` (whether via `instrument()` or the environment variable) only affect OpenAI clients obtained via `get_openai_client()` **after** the change is applied. Previously acquired clients are unaffected. +- To apply the new setting to all clients, call `AIProjectInstrumentor().instrument(enable_trace_context_propagation=)` before acquiring your OpenAI clients, or re-acquire the clients after making the change. -**Important Security and Privacy Considerations:** - -- **Trace IDs**: When trace context propagation is enabled, trace IDs are sent to Azure OpenAI and other external services. -- **Request Correlation**: Trace IDs allow Azure services to correlate requests from the same session or user across multiple API calls, which may have privacy implications depending on your use case. -- **Opt-in by Design**: This feature is disabled by default to give you explicit control over when trace context is propagated to external services. - -Only enable trace context propagation after carefully reviewing your observability, privacy and security requirements. +**Security and Privacy Considerations:** +- **Trace IDs are sent to external services**: The `traceparent` and `tracestate` headers from your client-side originating spans are injected into requests sent to service. This enables end-to-end distributed tracing, but note that the trace identifier may be shared beyond the initial API call. +- **Enabled by Default**: If you have privacy or compliance requirements that prohibit sharing trace identifiers with services, disable trace context propagation by setting `enable_trace_context_propagation=False` or the environment variable to `false`. #### Controlling baggage propagation @@ -1236,6 +1234,8 @@ When trace context propagation is enabled, you can separately control whether th If no value is provided for the `enable_baggage_propagation` parameter with the `AIProjectInstrumentor.instrument()` call and the environment variable is not set, the value defaults to `false` and baggage is not included. +**Note:** The `enable_baggage_propagation` flag is evaluated dynamically on each request, so changes take effect **immediately** for all clients that have the trace context propagation hook registered. However, the hook is only registered on clients acquired via `get_openai_client()` **while trace context propagation was enabled**. Clients acquired when trace context propagation was disabled will never propagate baggage, regardless of the `enable_baggage_propagation` value. + **Why is baggage propagation separate?** The baggage header can contain arbitrary key-value pairs added anywhere in your application's trace context. Unlike trace IDs (which are randomly generated identifiers), baggage may contain: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py index d10b543996a1..1a22ca314704 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py @@ -234,14 +234,20 @@ def instrument( spans to be correlated with client-side spans. `True` will enable it, `False` will disable it. If no value is provided, then the value read from environment variable AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION is used. If the environment - variable is not found, then the value will default to `False`. + variable is not found, then the value will default to `True`. + Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after + the change; previously acquired clients are unaffected. :type enable_trace_context_propagation: bool, optional :param enable_baggage_propagation: Whether to include baggage headers in trace context propagation. Only applies when enable_trace_context_propagation is True. `True` will enable baggage propagation, `False` will disable it. If no value is provided, then the value read from environment variable AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the environment variable is not found, then the value will default to `False`. - Note: Baggage may contain sensitive application data. + Note: Baggage may contain sensitive application data. This value is evaluated dynamically on + each request, so changes apply immediately — but only for OpenAI clients that had the trace + context propagation hook registered at acquisition time (i.e. clients obtained via + get_openai_client() while enable_trace_context_propagation was True). Clients acquired when + trace context propagation was disabled will never propagate baggage regardless of this value. :type enable_baggage_propagation: bool, optional """ @@ -330,13 +336,20 @@ def instrument( :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. `True` will enable it, `False` will disable it. If no value is provided, then the value read from environment variable AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION - is used. If the environment variable is not found, then the value will default to `False`. + is used. If the environment variable is not found, then the value will default to `True`. + Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after + the change; previously acquired clients are unaffected. :type enable_trace_context_propagation: bool, optional :param enable_baggage_propagation: Whether to include baggage in trace context propagation. Only applies when enable_trace_context_propagation is True. `True` will enable it, `False` will disable it. If no value is provided, then the value read from environment variable AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the environment variable is not found, then the value will default to `False`. + Note: This value is evaluated dynamically on each request, so changes apply immediately — + but only for OpenAI clients that had the trace context propagation hook registered at + acquisition time (i.e. clients obtained via get_openai_client() while + enable_trace_context_propagation was True). Clients acquired when trace context propagation + was disabled will never propagate baggage regardless of this value. :type enable_baggage_propagation: bool, optional """ @@ -347,7 +360,7 @@ def instrument( if enable_trace_context_propagation is None: var_value = os.environ.get("AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION") - enable_trace_context_propagation = self._str_to_bool(var_value) + enable_trace_context_propagation = True if var_value is None else self._str_to_bool(var_value) if enable_baggage_propagation is None: var_value = os.environ.get("AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE") diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py index ac2a033ec164..f60fa074c624 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py @@ -185,6 +185,175 @@ def test_experimental_genai_tracing_gate(self, env_value: Optional[str], should_ trace._TRACER_PROVIDER = None os.environ.pop("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", None) + @pytest.mark.parametrize( + "env_value, instrument_kwarg, expected_propagated, baggage_env_value, baggage_kwarg, expected_baggage", + [ + # --- trace context propagation (baggage defaults off) --- + (None, None, True, None, None, False), # default: traceparent on, baggage off + ("true", None, True, None, None, False), # explicit true + ("True", None, True, None, None, False), # case-insensitive + ("TRUE", None, True, None, None, False), # case-insensitive + ("false", None, False, None, None, False), # propagation off → no headers at all + ("False", None, False, None, None, False), # case-insensitive + (None, True, True, None, None, False), # parameter override: True + (None, False, False, None, None, False), # parameter override: False + # --- baggage propagation (trace context on via default) --- + (None, None, True, "true", None, True), # baggage env=true → baggage header present + (None, None, True, "false", None, False), # baggage env=false → baggage header absent + (None, None, True, None, True, True), # baggage kwarg=True → baggage header present + (None, None, True, None, False, False), # baggage kwarg=False → baggage header absent + # baggage enabled but trace propagation disabled → hook not installed, baggage still absent + (None, False, False, None, True, False), + ], + ) + def test_trace_context_propagation( + self, + env_value: Optional[str], + instrument_kwarg: Optional[bool], + expected_propagated: bool, + baggage_env_value: Optional[str], + baggage_kwarg: Optional[bool], + expected_baggage: bool, + ): + """ + Test that trace context and baggage propagation are controlled correctly by their + respective environment variables and instrument() parameters, and that the traceparent + and baggage headers are (or are not) injected into outgoing HTTP requests accordingly. + + Uses a mock httpx transport to capture the outgoing request and inspect its headers, + and exercises the same _inject_openai_client wrapper that the instrumented + get_openai_client() uses, so no live service is required. + + Args: + env_value: Value for AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION, or None to leave unset. + instrument_kwarg: Value passed as enable_trace_context_propagation to instrument(), or None to omit. + expected_propagated: Whether traceparent should appear in outgoing request headers. + baggage_env_value: Value for AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE, or None to leave unset. + baggage_kwarg: Value passed as enable_baggage_propagation to instrument(), or None to omit. + expected_baggage: Whether baggage should appear in outgoing request headers. + """ + import httpx + from openai import OpenAI + from opentelemetry import baggage as otel_baggage + from opentelemetry import trace + from opentelemetry.sdk.trace import TracerProvider + from opentelemetry.sdk.trace.export import SimpleSpanProcessor + from memory_trace_exporter import MemoryTraceExporter # pylint: disable=import-error + + TRACE_ENV_VAR = "AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION" + BAGGAGE_ENV_VAR = "AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE" + AIProjectInstrumentor().uninstrument() + os.environ.pop(TRACE_ENV_VAR, None) + os.environ.pop(BAGGAGE_ENV_VAR, None) + if env_value is not None: + os.environ[TRACE_ENV_VAR] = env_value + if baggage_env_value is not None: + os.environ[BAGGAGE_ENV_VAR] = baggage_env_value + + from opentelemetry.baggage.propagation import W3CBaggagePropagator + from opentelemetry.trace.propagation.tracecontext import TraceContextTextMapPropagator + from opentelemetry.propagators.composite import CompositePropagator + from opentelemetry import propagate as otel_propagate + + tracer_provider = TracerProvider() + trace._TRACER_PROVIDER = tracer_provider + exporter = MemoryTraceExporter() + tracer_provider.add_span_processor(SimpleSpanProcessor(exporter)) + + # Ensure both W3C TraceContext and Baggage propagators are active. + # The test environment may only have TraceContext registered by default. + original_propagator = otel_propagate.get_global_textmap() + otel_propagate.set_global_textmap( + CompositePropagator([TraceContextTextMapPropagator(), W3CBaggagePropagator()]) + ) + + try: + os.environ["AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING"] = "true" + instrument_kwargs: dict = {} + if instrument_kwarg is not None: + instrument_kwargs["enable_trace_context_propagation"] = instrument_kwarg + if baggage_kwarg is not None: + instrument_kwargs["enable_baggage_propagation"] = baggage_kwarg + AIProjectInstrumentor().instrument(**instrument_kwargs) + + import azure.ai.projects.telemetry._ai_project_instrumentor as _instrumentor_mod + + # Verify the module-level global for trace context propagation reflects the correct state. + assert _instrumentor_mod._trace_context_propagation_enabled == expected_propagated, ( # pylint: disable=protected-access + f"Expected _trace_context_propagation_enabled={expected_propagated} " + f"for env={env_value!r} / kwarg={instrument_kwarg!r}" + ) + + # ---- Mock transport: capture outgoing HTTP request headers ---- + class CapturingTransport(httpx.BaseTransport): + def __init__(self): + self.last_request: Optional[httpx.Request] = None + + def handle_request(self, request: httpx.Request) -> httpx.Response: + self.last_request = request + return httpx.Response(200, content=b"{}") + + transport = CapturingTransport() + http_client = httpx.Client(transport=transport) + openai_client = OpenAI(api_key="fake-key", base_url="http://fake.test/", http_client=http_client) + + # Exercise the same wrapper that get_openai_client() uses after instrumentation. + # _inject_openai_client(factory, ...) wraps the factory and conditionally registers + # the traceparent/baggage-injection hook based on the propagation globals. + def fake_factory(*args, **kwargs): + return openai_client + + wrapped_factory = AIProjectInstrumentor()._impl._inject_openai_client( # pylint: disable=protected-access + fake_factory, None, "get_openai_client" + ) + wrapped_factory() # This either registers the hook or not + + # Make an HTTP request within an active span with baggage seeded into the context, + # so OTel propagate.inject() has both a trace context and baggage to inject. + # Note: baggage must be attached *before* starting the span. Passing context= to + # start_as_current_span only sets the parent span; the new span is still attached + # on top of the current global context, so baggage set only in that context arg + # would be silently dropped. + from opentelemetry import context as otel_context + tracer = trace.get_tracer(__name__) + ctx_with_baggage = otel_baggage.set_baggage("test-key", "test-value") + token = otel_context.attach(ctx_with_baggage) + try: + with tracer.start_as_current_span("test_span"): + http_client.get("http://fake.test/test") + finally: + otel_context.detach(token) + + assert transport.last_request is not None + header_names = [h.lower() for h in transport.last_request.headers.keys()] + + if expected_propagated: + assert "traceparent" in header_names, ( + f"Expected traceparent header to be present (env={env_value!r}, kwarg={instrument_kwarg!r})" + ) + else: + assert "traceparent" not in header_names, ( + f"Expected traceparent header to be absent (env={env_value!r}, kwarg={instrument_kwarg!r})" + ) + + if expected_baggage: + assert "baggage" in header_names, ( + f"Expected baggage header to be present (baggage_env={baggage_env_value!r}, baggage_kwarg={baggage_kwarg!r})" + ) + else: + assert "baggage" not in header_names, ( + f"Expected baggage header to be absent (baggage_env={baggage_env_value!r}, baggage_kwarg={baggage_kwarg!r})" + ) + + finally: + exporter.shutdown() + AIProjectInstrumentor().uninstrument() + trace._TRACER_PROVIDER = None + otel_propagate.set_global_textmap(original_propagator) + os.environ.pop(TRACE_ENV_VAR, None) + os.environ.pop(BAGGAGE_ENV_VAR, None) + os.environ.pop("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", None) + @pytest.mark.parametrize( "env1, env2, expected", [ From 8dc5bb8cf39d9bfde103aaec6f5e5d49a861dd7a Mon Sep 17 00:00:00 2001 From: Waqas Javed <7674577+w-javed@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:47:47 -0700 Subject: [PATCH 24/36] Custom Eval - Upload (#45678) * Adding-Upload-Evaluator * Adding-Upload-Evaluator * Adding-Upload-Evaluator * Adding-Upload-Evaluator-aio * rename * added - eval and eval run * fix * adding tests * updated as per review --- .../ai/projects/aio/operations/_patch.py | 4 +- .../aio/operations/_patch_evaluators_async.py | 203 +++++++ .../azure/ai/projects/operations/_patch.py | 4 +- .../projects/operations/_patch_evaluators.py | 201 +++++++ .../answer_length_evaluator.py | 14 + .../common_util/__init__.py | 0 .../friendly_evaluator/common_util/util.py | 72 +++ .../friendly_evaluator/friendly_evaluator.py | 62 +++ .../sample_eval_upload_custom_evaluator.py | 223 ++++++++ .../sample_eval_upload_friendly_evaluator.py | 246 +++++++++ .../evaluators/test_evaluators_upload.py | 450 ++++++++++++++++ .../test_evaluators_upload_async.py | 497 ++++++++++++++++++ 12 files changed, 1974 insertions(+), 2 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/__init__.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/util.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_custom_evaluator.py create mode 100644 sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_friendly_evaluator.py create mode 100644 sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py create mode 100644 sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index 9e5b1872d76d..2515113bbf86 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -12,12 +12,12 @@ from ._patch_agents_async import AgentsOperations from ._patch_datasets_async import DatasetsOperations from ._patch_evaluation_rules_async import EvaluationRulesOperations +from ._patch_evaluators_async import EvaluatorsOperations as BetaEvaluatorsOperations from ._patch_telemetry_async import TelemetryOperations from ._patch_connections_async import ConnectionsOperations from ._patch_memories_async import BetaMemoryStoresOperations from ._operations import ( BetaEvaluationTaxonomiesOperations, - BetaEvaluatorsOperations, BetaInsightsOperations, BetaOperations as GeneratedBetaOperations, BetaRedTeamsOperations, @@ -53,6 +53,8 @@ class BetaOperations(GeneratedBetaOperations): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + # Replace with patched class that includes upload() + self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) # Replace with patched class that includes begin_update_memories self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py new file mode 100644 index 000000000000..2a0bf8dc5829 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -0,0 +1,203 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" +import os +import logging +from typing import Any, IO, Tuple, Optional, Union +from pathlib import Path +from urllib.parse import urlsplit +from azure.storage.blob.aio import ContainerClient +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError +from ._operations import BetaEvaluatorsOperations as EvaluatorsOperationsGenerated, JSON +from ...models._models import ( + EvaluatorVersion, +) + +logger = logging.getLogger(__name__) + + +class EvaluatorsOperations(EvaluatorsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`beta.evaluators` attribute. + """ + + async def _start_pending_upload_and_get_container_client( + self, + name: str, + version: str, + connection_name: Optional[str] = None, + ) -> Tuple[ContainerClient, str, str]: + """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI.""" + + request_body: dict = {} + if connection_name: + request_body["connectionName"] = connection_name + + pending_upload_response = await self.pending_upload( + name=name, + version=version, + pending_upload_request=request_body, + ) + + # The service returns blobReferenceForConsumption + blob_ref = pending_upload_response.get("blobReferenceForConsumption") + if not blob_ref: + raise ValueError("Blob reference is not present in the pending upload response") + + credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None + if not credential: + raise ValueError("SAS credential is not present in the pending upload response") + + sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None + if not sas_uri: + raise ValueError("SAS URI is missing or empty in the pending upload response") + + blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None + if not blob_uri: + raise ValueError("Blob URI is missing or empty in the pending upload response") + + return ( + ContainerClient.from_container_url(container_url=sas_uri), + version, + blob_uri, + ) + + async def _get_next_version(self, name: str) -> str: + """Get the next version number for an evaluator by fetching existing versions.""" + try: + versions = [] + async for v in self.list_versions(name=name): + versions.append(v) + if versions: + numeric_versions = [] + for v in versions: + ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) + if ver and ver.isdigit(): + numeric_versions.append(int(ver)) + if numeric_versions: + return str(max(numeric_versions) + 1) + return "1" + except ResourceNotFoundError: + return "1" + + @distributed_trace_async + async def upload( + self, + name: str, + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + *, + folder: str, + connection_name: Optional[str] = None, + **kwargs: Any, + ) -> EvaluatorVersion: + """Upload all files in a folder to blob storage and create a code-based evaluator version + that references the uploaded code. + + This method calls startPendingUpload to get a SAS URI, uploads files from the folder + to blob storage, then creates an evaluator version referencing the uploaded blob. + + The version is automatically determined by incrementing the latest existing version. + + :param name: The name of the evaluator. Required. + :type name: str + :param evaluator_version: The evaluator version definition. This is the same object accepted + by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, + IO[bytes]. Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :keyword folder: Path to the folder containing the evaluator Python code. Required. + :paramtype folder: str + :keyword connection_name: The name of an Azure Storage Account connection where the files + should be uploaded. If not specified, the default Azure Storage Account connection will be + used. Optional. + :paramtype connection_name: str + :return: The created evaluator version. + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + path_folder = Path(folder) + if not path_folder.exists(): + raise ValueError(f"The provided folder `{folder}` does not exist.") + if path_folder.is_file(): + raise ValueError("The provided path is a file, not a folder.") + + version = await self._get_next_version(name) + logger.info("[upload] Auto-resolved version to '%s'.", version) + + # Get SAS URI via startPendingUpload + container_client, _, blob_uri = await self._start_pending_upload_and_get_container_client( + name=name, + version=version, + connection_name=connection_name, + ) + + async with container_client: + # Upload all files from the folder (including nested subdirectories) + skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} + skip_extensions = {".pyc", ".pyo"} + files_uploaded: bool = False + for root, dirs, files in os.walk(folder): + # Prune directories we don't want to traverse + dirs[:] = [d for d in dirs if d not in skip_dirs] + for file in files: + if any(file.endswith(ext) for ext in skip_extensions): + continue + file_path = os.path.join(root, file) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") + logger.debug( + "[upload] Start uploading file `%s` as blob `%s`.", + file_path, + blob_name, + ) + with open(file=file_path, mode="rb") as data: + try: + await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + except HttpResponseError as e: + if e.error_code == "AuthorizationPermissionMismatch": + storage_account = urlsplit(container_client.url).hostname + raise HttpResponseError( + message=( + f"Failed to upload file '{blob_name}' to blob storage: " + f"permission denied. Ensure the identity that signed the SAS token " + f"has the 'Storage Blob Data Contributor' role on the storage account " + f"'{storage_account}'. " + f"Original error: {e.message}" + ), + response=e.response, + ) from e + raise + logger.debug("[upload] Done uploading file") + files_uploaded = True + logger.debug("[upload] Done uploading all files.") + + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + # Set the blob_uri in the evaluator version definition + if isinstance(evaluator_version, dict): + definition = evaluator_version.get("definition", {}) + if isinstance(definition, dict): + definition["blob_uri"] = blob_uri + else: + definition.blob_uri = blob_uri + else: + if hasattr(evaluator_version, "definition") and evaluator_version.definition: + evaluator_version.definition.blob_uri = blob_uri + + result = await self.create_version( + name=name, + evaluator_version=evaluator_version, + ) + + return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index de30f901a967..cdba64dd7f84 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -12,12 +12,12 @@ from ._patch_agents import AgentsOperations from ._patch_datasets import DatasetsOperations from ._patch_evaluation_rules import EvaluationRulesOperations +from ._patch_evaluators import EvaluatorsOperations as BetaEvaluatorsOperations from ._patch_telemetry import TelemetryOperations from ._patch_connections import ConnectionsOperations from ._patch_memories import BetaMemoryStoresOperations from ._operations import ( BetaEvaluationTaxonomiesOperations, - BetaEvaluatorsOperations, BetaInsightsOperations, BetaOperations as GeneratedBetaOperations, BetaRedTeamsOperations, @@ -53,6 +53,8 @@ class BetaOperations(GeneratedBetaOperations): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) + # Replace with patched class that includes upload() + self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) # Replace with patched class that includes begin_update_memories self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py new file mode 100644 index 000000000000..73f3904a8272 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -0,0 +1,201 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" +import os +import logging +from typing import Any, IO, Tuple, Optional, Union +from pathlib import Path +from urllib.parse import urlsplit +from azure.storage.blob import ContainerClient +from azure.core.tracing.decorator import distributed_trace +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError +from ._operations import BetaEvaluatorsOperations as EvaluatorsOperationsGenerated, JSON +from ..models._models import ( + EvaluatorVersion, +) + +logger = logging.getLogger(__name__) + + +class EvaluatorsOperations(EvaluatorsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`beta.evaluators` attribute. + """ + + def _start_pending_upload_and_get_container_client( + self, + name: str, + version: str, + connection_name: Optional[str] = None, + ) -> Tuple[ContainerClient, str, str]: + """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI.""" + + request_body: dict = {} + if connection_name: + request_body["connectionName"] = connection_name + + pending_upload_response = self.pending_upload( + name=name, + version=version, + pending_upload_request=request_body, + ) + + # The service returns blobReferenceForConsumption + blob_ref = pending_upload_response.get("blobReferenceForConsumption") + if not blob_ref: + raise ValueError("Blob reference is not present in the pending upload response") + + credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None + if not credential: + raise ValueError("SAS credential is not present in the pending upload response") + + sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None + if not sas_uri: + raise ValueError("SAS URI is missing or empty in the pending upload response") + + blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None + if not blob_uri: + raise ValueError("Blob URI is missing or empty in the pending upload response") + + return ( + ContainerClient.from_container_url(container_url=sas_uri), + version, + blob_uri, + ) + + def _get_next_version(self, name: str) -> str: + """Get the next version number for an evaluator by fetching existing versions.""" + try: + versions = list(self.list_versions(name=name)) + if versions: + numeric_versions = [] + for v in versions: + ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) + if ver and ver.isdigit(): + numeric_versions.append(int(ver)) + if numeric_versions: + return str(max(numeric_versions) + 1) + return "1" + except ResourceNotFoundError: + return "1" + + @distributed_trace + def upload( + self, + name: str, + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + *, + folder: str, + connection_name: Optional[str] = None, + **kwargs: Any, + ) -> EvaluatorVersion: + """Upload all files in a folder to blob storage and create a code-based evaluator version + that references the uploaded code. + + This method calls startPendingUpload to get a SAS URI, uploads files from the folder + to blob storage, then creates an evaluator version referencing the uploaded blob. + + The version is automatically determined by incrementing the latest existing version. + + :param name: The name of the evaluator. Required. + :type name: str + :param evaluator_version: The evaluator version definition. This is the same object accepted + by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, + IO[bytes]. Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :keyword folder: Path to the folder containing the evaluator Python code. Required. + :paramtype folder: str + :keyword connection_name: The name of an Azure Storage Account connection where the files + should be uploaded. If not specified, the default Azure Storage Account connection will be + used. Optional. + :paramtype connection_name: str + :return: The created evaluator version. + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + path_folder = Path(folder) + if not path_folder.exists(): + raise ValueError(f"The provided folder `{folder}` does not exist.") + if path_folder.is_file(): + raise ValueError("The provided path is a file, not a folder.") + + version = self._get_next_version(name) + logger.info("[upload] Auto-resolved version to '%s'.", version) + + # Get SAS URI via startPendingUpload + container_client, _, blob_uri = self._start_pending_upload_and_get_container_client( + name=name, + version=version, + connection_name=connection_name, + ) + + with container_client: + # Upload all files from the folder (including nested subdirectories) + skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} + skip_extensions = {".pyc", ".pyo"} + files_uploaded: bool = False + for root, dirs, files in os.walk(folder): + # Prune directories we don't want to traverse + dirs[:] = [d for d in dirs if d not in skip_dirs] + for file in files: + if any(file.endswith(ext) for ext in skip_extensions): + continue + file_path = os.path.join(root, file) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") + logger.debug( + "[upload] Start uploading file `%s` as blob `%s`.", + file_path, + blob_name, + ) + with open(file=file_path, mode="rb") as data: + try: + container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + except HttpResponseError as e: + if e.error_code == "AuthorizationPermissionMismatch": + storage_account = urlsplit(container_client.url).hostname + raise HttpResponseError( + message=( + f"Failed to upload file '{blob_name}' to blob storage: " + f"permission denied. Ensure the identity that signed the SAS token " + f"has the 'Storage Blob Data Contributor' role on the storage account " + f"'{storage_account}'. " + f"Original error: {e.message}" + ), + response=e.response, + ) from e + raise + logger.debug("[upload] Done uploading file") + files_uploaded = True + logger.debug("[upload] Done uploading all files.") + + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + # Set the blob_uri in the evaluator version definition + if isinstance(evaluator_version, dict): + definition = evaluator_version.get("definition", {}) + if isinstance(definition, dict): + definition["blob_uri"] = blob_uri + else: + definition.blob_uri = blob_uri + else: + if hasattr(evaluator_version, "definition") and evaluator_version.definition: + evaluator_version.definition.blob_uri = blob_uri + + result = self.create_version( + name=name, + evaluator_version=evaluator_version, + ) + + return result diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py new file mode 100644 index 000000000000..1fa95ab19b1d --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py @@ -0,0 +1,14 @@ +"""Custom evaluator that measures the length of a response.""" + + +class AnswerLengthEvaluator: + def __init__(self, *, model_config): + self.model_config = model_config + + def __call__(self, *args, **kwargs): + return {"result": evaluate_answer_length(kwargs.get("response"))} + + +def evaluate_answer_length(answer: str): + return len(answer) + diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/__init__.py b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/util.py b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/util.py new file mode 100644 index 000000000000..7499261ba7c6 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/common_util/util.py @@ -0,0 +1,72 @@ +"""Utility functions for custom evaluators.""" + +FRIENDLINESS_SYSTEM_PROMPT = """You are an expert evaluator that assesses how friendly, warm, and approachable +a response is. You evaluate responses on a scale of 1 to 5 based on the following criteria: + +Score 1 (Very Unfriendly): The response is cold, dismissive, rude, or hostile. +Score 2 (Unfriendly): The response is curt, impersonal, or lacks warmth. +Score 3 (Neutral): The response is acceptable but neither particularly friendly nor unfriendly. +Score 4 (Friendly): The response is warm, polite, and shows genuine interest in helping. +Score 5 (Very Friendly): The response is exceptionally warm, encouraging, empathetic, and makes the user feel valued. + +You MUST respond in the following JSON format only: +{ + "score": , + "label": "", + "reason": "", + "explanation": "" +} + +A score of 3 or above is considered "Pass", below 3 is "Fail". +""" + + +def build_evaluation_messages(query: str, response: str) -> list: + """Build the messages list for the LLM evaluation call. + + :param query: The original user query. + :param response: The response to evaluate for friendliness. + :return: A list of message dicts for the chat completion API. + """ + return [ + {"role": "system", "content": FRIENDLINESS_SYSTEM_PROMPT}, + { + "role": "user", + "content": ( + f"Please evaluate the friendliness of the following response.\n\n" + f"Original query: {query}\n\n" + f"Response to evaluate: {response}" + ), + }, + ] + + +def parse_evaluation_result(raw_result: str) -> dict: + """Parse the LLM's JSON response into a structured evaluation result. + + :param raw_result: The raw string output from the LLM. + :return: A dict with score, label, reason, and explanation. + """ + import json + + try: + # Try to extract JSON from the response (handle markdown code blocks) + text = raw_result.strip() + if text.startswith("```"): + text = text.split("\n", 1)[1] if "\n" in text else text[3:] + text = text.rsplit("```", 1)[0] + result = json.loads(text.strip()) + score = int(result.get("score", 3)) + return { + "score": max(1, min(5, score)), + "label": result.get("label", "Pass" if score >= 3 else "Fail"), + "reason": result.get("reason", "No reason provided"), + "explanation": result.get("explanation", "No explanation provided"), + } + except (json.JSONDecodeError, ValueError, KeyError): + return { + "score": 3, + "label": "Pass", + "reason": "Could not parse LLM response", + "explanation": f"Raw LLM output: {raw_result}", + } diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py new file mode 100644 index 000000000000..c58ff350ba25 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py @@ -0,0 +1,62 @@ +"""Custom evaluator that uses an LLM to assess the friendliness of a response.""" + +from openai import AzureOpenAI +from common_util.util import build_evaluation_messages, parse_evaluation_result + + +class FriendlyEvaluator: + """Evaluates how friendly and approachable a response is using an LLM judge. + + This evaluator sends the query and response to an LLM, which returns a + friendliness score (1-5), a pass/fail label, a reason, and a detailed explanation. + + :param model_config: A dict containing Azure OpenAI connection info. Expected keys: + - azure_endpoint: The Azure OpenAI endpoint URL. + - azure_deployment: The deployment/model name. + - api_version: The API version (default: "2024-06-01"). + - api_key: (Optional) The API key. If not provided, DefaultAzureCredential is used. + """ + + def __init__(self, *, model_config: dict): + self.model_config = model_config + api_key = model_config.get("api_key") + + if api_key: + self.client = AzureOpenAI( + azure_endpoint=model_config["azure_endpoint"], + api_key=api_key, + api_version=model_config.get("api_version", "2024-06-01"), + ) + else: + from azure.identity import DefaultAzureCredential, get_bearer_token_provider + + token_provider = get_bearer_token_provider( + DefaultAzureCredential(), + "https://cognitiveservices.azure.com/.default", + ) + self.client = AzureOpenAI( + azure_endpoint=model_config["azure_endpoint"], + azure_ad_token_provider=token_provider, + api_version=model_config.get("api_version", "2024-06-01"), + ) + + self.deployment = model_config["azure_deployment"] + + def __call__(self, *, query: str, response: str, **kwargs) -> dict: + """Evaluate the friendliness of a response. + + :param query: The original user query. + :param response: The response to evaluate. + :return: A dict with score, label, reason, and explanation. + """ + messages = build_evaluation_messages(query, response) + + completion = self.client.chat.completions.create( + model=self.deployment, + messages=messages, + temperature=0.0, + max_tokens=500, + ) + + raw_result = completion.choices[0].message.content + return parse_evaluation_result(raw_result) diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_custom_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_custom_evaluator.py new file mode 100644 index 000000000000..efe1267f104b --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_custom_evaluator.py @@ -0,0 +1,223 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + Given an AIProjectClient, this sample demonstrates how to: + 1. Upload a local folder containing custom evaluator Python code and + register it as a code-based evaluator version using `evaluators.upload()`. + 2. Create an evaluation (eval) that references the uploaded evaluator. + 3. Run the evaluation with inline data and poll for results. + +USAGE: + python sample_eval_upload_custom_evaluator.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b4" azure-storage-blob python-dotenv azure-identity openai + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint, as found in the overview page of your + Microsoft Foundry project. It has the form: https://.services.ai.azure.com/api/projects/. + 2) FOUNDRY_MODEL_NAME - Optional. The name of the model deployment to use for evaluation. +""" + +import os +import time +import random +import string +from pathlib import Path +from pprint import pprint + +from dotenv import load_dotenv +from openai.types.evals.create_eval_jsonl_run_data_source_param import ( + CreateEvalJSONLRunDataSourceParam, + SourceFileContent, + SourceFileContentContent, +) +from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import ( + CodeBasedEvaluatorDefinition, + EvaluatorCategory, + EvaluatorMetric, + EvaluatorMetricType, + EvaluatorMetricDirection, + EvaluatorType, + EvaluatorVersion, +) + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME") + +# The folder containing the AnswerLength evaluator code, relative to this sample file. +local_upload_folder = str(Path(__file__).parent / "custom_evaluators" / "answer_length_evaluator") + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as client, +): + # --------------------------------------------------------------- + # 1. Upload evaluator code and create evaluator version + # upload() internally calls startPendingUpload to get a SAS URI, + # uploads the folder contents to blob storage, then creates the + # evaluator version with the blob URI. + # --------------------------------------------------------------- + suffix = "".join(random.choices(string.ascii_lowercase, k=5)) + evaluator_name = f"answer_length_evaluator_{suffix}" + evaluator_version = EvaluatorVersion( + evaluator_type=EvaluatorType.CUSTOM, + categories=[EvaluatorCategory.QUALITY], + display_name="Answer Length Evaluator", + description="Custom evaluator to calculate length of content", + definition=CodeBasedEvaluatorDefinition( + entry_point="answer_length_evaluator:AnswerLengthEvaluator", + init_parameters={ + "type": "object", + "properties": {"model_config": {"type": "string"}}, + "required": ["model_config"], + }, + data_schema={ + "type": "object", + "properties": { + "query": {"type": "string"}, + "response": {"type": "string"}, + }, + "required": ["query", "response"], + }, + metrics={ + "score": EvaluatorMetric( + type=EvaluatorMetricType.ORDINAL, + desirable_direction=EvaluatorMetricDirection.INCREASE, + min_value=1, + max_value=5, + ) + }, + ), + ) + + print("Uploading custom evaluator code and creating evaluator version...") + code_evaluator = project_client.beta.evaluators.upload( + name=evaluator_name, + evaluator_version=evaluator_version, + folder=local_upload_folder, + ) + + print(f"Evaluator created: name={code_evaluator.name}, version={code_evaluator.version}") + print(f"Evaluator ID: {code_evaluator.id}") + pprint(code_evaluator) + + # --------------------------------------------------------------- + # 2. Create an evaluation referencing the uploaded evaluator + # --------------------------------------------------------------- + data_source_config = DataSourceConfigCustom( + { + "type": "custom", + "item_schema": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "response": {"type": "string"}, + }, + "required": ["query", "response"], + }, + "include_sample_schema": True, + } + ) + + testing_criteria = [ + { + "type": "azure_ai_evaluator", + "name": evaluator_name, + "evaluator_name": evaluator_name, + "initialization_parameters": { + "model_config": f"{model_deployment_name}", + }, + } + ] + + print("\nCreating evaluation...") + eval_object = client.evals.create( + name=f"Answer Length Evaluation - {suffix}", + data_source_config=data_source_config, + testing_criteria=testing_criteria, # type: ignore + ) + print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") + + # --------------------------------------------------------------- + # 3. Run the evaluation with inline data + # --------------------------------------------------------------- + print("\nCreating evaluation run with inline data...") + eval_run_object = client.evals.runs.create( + eval_id=eval_object.id, + name=f"Answer Length Eval Run - {suffix}", + metadata={"team": "eval-exp", "scenario": "answer-length-v1"}, + data_source=CreateEvalJSONLRunDataSourceParam( + type="jsonl", + source=SourceFileContent( + type="file_content", + content=[ + SourceFileContentContent( + item={ + "query": "What is the capital of France?", + "response": "Paris", + } + ), + SourceFileContentContent( + item={ + "query": "Explain quantum computing", + "response": "Quantum computing leverages quantum mechanical phenomena like superposition and entanglement to process information in fundamentally different ways than classical computers.", + } + ), + SourceFileContentContent( + item={ + "query": "What is AI?", + "response": "AI stands for Artificial Intelligence. It is a branch of computer science that aims to create intelligent machines that can perform tasks that typically require human intelligence, such as visual perception, speech recognition, decision-making, and language translation.", + } + ), + SourceFileContentContent( + item={ + "query": "Say hello", + "response": "Hi!", + } + ), + ], + ), + ), + ) + + print(f"Evaluation run created (id: {eval_run_object.id})") + pprint(eval_run_object) + + # --------------------------------------------------------------- + # 4. Poll for evaluation run completion + # --------------------------------------------------------------- + while True: + run = client.evals.runs.retrieve(run_id=eval_run_object.id, eval_id=eval_object.id) + if run.status in ("completed", "failed"): + print(f"\nEvaluation run finished with status: {run.status}") + output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) + pprint(output_items) + print(f"\nEvaluation run Report URL: {run.report_url}") + break + time.sleep(5) + print("Waiting for evaluation run to complete...") + + # --------------------------------------------------------------- + # 5. Cleanup (uncomment to delete) + # --------------------------------------------------------------- + # print("\nCleaning up...") + # project_client.beta.evaluators.delete_version( + # name=code_evaluator.name, + # version=code_evaluator.version, + # ) + # client.evals.delete(eval_id=eval_object.id) + # print("Cleanup done.") + print("\nDone - upload, eval creation, and eval run verified successfully.") diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_friendly_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_friendly_evaluator.py new file mode 100644 index 000000000000..ea972c4f348e --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/evaluations/sample_eval_upload_friendly_evaluator.py @@ -0,0 +1,246 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + Given an AIProjectClient, this sample demonstrates how to: + 1. Upload a custom LLM-based evaluator (FriendlyEvaluator) with nested + folder structure (common_util/) using `evaluators.upload()`. + 2. Create an evaluation (eval) that references the uploaded evaluator. + 3. Run the evaluation with inline data and poll for results. + + The FriendlyEvaluator calls Azure OpenAI to judge the friendliness of a + response and returns score, label, reason, and explanation. + +USAGE: + python sample_eval_upload_friendly_evaluator.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.0b4" azure-storage-blob python-dotenv azure-identity openai + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - Required. The Azure AI Project endpoint. + 2) FOUNDRY_MODEL_NAME - Optional. The name of the model deployment to use for evaluation. +""" + +import os +import time +import random +import string +from pathlib import Path +from pprint import pprint + +from dotenv import load_dotenv +from openai.types.evals.create_eval_jsonl_run_data_source_param import ( + CreateEvalJSONLRunDataSourceParam, + SourceFileContent, + SourceFileContentContent, +) +from openai.types.eval_create_params import DataSourceConfigCustom +from azure.identity import DefaultAzureCredential +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import ( + CodeBasedEvaluatorDefinition, + EvaluatorCategory, + EvaluatorMetric, + EvaluatorMetricType, + EvaluatorMetricDirection, + EvaluatorType, + EvaluatorVersion, +) + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] +model_deployment_name = os.environ.get("FOUNDRY_MODEL_NAME") +azure_openai_endpoint = os.environ["AZURE_OPENAI_ENDPOINT"] +azure_openai_api_key = os.environ["AZURE_OPENAI_API_KEY"] + +# The folder containing the FriendlyEvaluator code, including common_util/ subfolder +local_upload_folder = str(Path(__file__).parent / "custom_evaluators" / "friendly_evaluator") + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + project_client.get_openai_client() as client, +): + # --------------------------------------------------------------- + # 1. Upload evaluator code and create evaluator version + # The folder structure uploaded is: + # friendly_evaluator/ + # friendly_evaluator.py <- entry point + # common_util/ + # __init__.py + # util.py <- helper functions + # --------------------------------------------------------------- + suffix = "".join(random.choices(string.ascii_lowercase, k=5)) + evaluator_name = f"friendly_evaluator_{suffix}" + + evaluator_version = EvaluatorVersion( + evaluator_type=EvaluatorType.CUSTOM, + categories=[EvaluatorCategory.QUALITY], + display_name="Friendliness Evaluator", + description="LLM-based evaluator that scores how friendly a response is (1-5)", + definition=CodeBasedEvaluatorDefinition( + entry_point="friendly_evaluator:FriendlyEvaluator", + init_parameters={ + "type": "object", + "properties": { + "model_config": { + "type": "object", + "description": "Azure OpenAI configuration for the LLM judge", + "properties": { + "azure_endpoint": {"type": "string"}, + "api_version": {"type": "string"}, + "api_key": {"type": "string"}, + }, + "required": ["azure_endpoint", "api_key"], + } + }, + "required": ["model_config"], + }, + data_schema={ + "type": "object", + "properties": { + "query": {"type": "string", "description": "The original user query"}, + "response": {"type": "string", "description": "The response to evaluate for friendliness"}, + }, + "required": ["query", "response"], + }, + metrics={ + "score": EvaluatorMetric( + type=EvaluatorMetricType.ORDINAL, + desirable_direction=EvaluatorMetricDirection.INCREASE, + min_value=1, + max_value=5, + ) + }, + ), + ) + + print("Uploading FriendlyEvaluator (with nested common_util folder)...") + friendly_evaluator = project_client.beta.evaluators.upload( + name=evaluator_name, + evaluator_version=evaluator_version, + folder=local_upload_folder, + ) + + print(f"\nEvaluator created: name={friendly_evaluator.name}, version={friendly_evaluator.version}") + print(f"Evaluator ID: {friendly_evaluator.id}") + pprint(friendly_evaluator) + + # --------------------------------------------------------------- + # 2. Create an evaluation referencing the uploaded evaluator + # --------------------------------------------------------------- + data_source_config = DataSourceConfigCustom( + { + "type": "custom", + "item_schema": { + "type": "object", + "properties": { + "query": {"type": "string"}, + "response": {"type": "string"}, + }, + "required": ["query", "response"], + }, + "include_sample_schema": True, + } + ) + + testing_criteria = [ + { + "type": "azure_ai_evaluator", + "name": evaluator_name, + "evaluator_name": evaluator_name, + "initialization_parameters": { + "model_config": { + "azure_endpoint": azure_openai_endpoint, + "api_key": f"{azure_openai_api_key}", + "api_version": "2024-06-01", + }, + }, + } + ] + + print("\nCreating evaluation...") + eval_object = client.evals.create( + name=f"Friendliness Evaluation - {suffix}", + data_source_config=data_source_config, + testing_criteria=testing_criteria, # type: ignore + ) + print(f"Evaluation created (id: {eval_object.id}, name: {eval_object.name})") + + # --------------------------------------------------------------- + # 3. Run the evaluation with inline data + # --------------------------------------------------------------- + print("\nCreating evaluation run with inline data...") + eval_run_object = client.evals.runs.create( + eval_id=eval_object.id, + name=f"Friendliness Eval Run - {suffix}", + metadata={"team": "eval-exp", "scenario": "friendliness-v1"}, + data_source=CreateEvalJSONLRunDataSourceParam( + type="jsonl", + source=SourceFileContent( + type="file_content", + content=[ + SourceFileContentContent( + item={ + "query": "How do I reset my password?", + "response": "Go to settings and click reset. That's it.", + } + ), + SourceFileContentContent( + item={ + "query": "I'm having trouble with my account", + "response": "I'm really sorry to hear you're having trouble! I'd love to help you get this sorted out. Could you tell me a bit more about what's happening so I can assist you better?", + } + ), + SourceFileContentContent( + item={ + "query": "Can you help me?", + "response": "Read the docs.", + } + ), + SourceFileContentContent( + item={ + "query": "What's the weather like today?", + "response": "Great question! While I'm not a weather service, I'd be happy to suggest some wonderful weather apps that can give you accurate forecasts. Would you like some recommendations? 😊", + } + ), + ], + ), + ), + ) + + print(f"Evaluation run created (id: {eval_run_object.id})") + pprint(eval_run_object) + + # --------------------------------------------------------------- + # 4. Poll for evaluation run completion + # --------------------------------------------------------------- + while True: + run = client.evals.runs.retrieve(run_id=eval_run_object.id, eval_id=eval_object.id) + if run.status in ("completed", "failed"): + print(f"\nEvaluation run finished with status: {run.status}") + output_items = list(client.evals.runs.output_items.list(run_id=run.id, eval_id=eval_object.id)) + pprint(output_items) + print(f"\nEvaluation run Report URL: {run.report_url}") + break + time.sleep(5) + print("Waiting for evaluation run to complete...") + + # --------------------------------------------------------------- + # 5. Cleanup (uncomment to delete) + # --------------------------------------------------------------- + # print("\nCleaning up...") + # project_client.beta.evaluators.delete_version( + # name=friendly_evaluator.name, + # version=friendly_evaluator.version, + # ) + # client.evals.delete(eval_id=eval_object.id) + # print("Cleanup done.") + print("\nDone - FriendlyEvaluator upload, eval creation, and eval run verified successfully.") diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py new file mode 100644 index 000000000000..a222b1d1db1d --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py @@ -0,0 +1,450 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import os +import tempfile +import pytest +from unittest.mock import MagicMock, patch, call +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError +from azure.ai.projects.operations._patch_evaluators import EvaluatorsOperations +from azure.ai.projects.models import EvaluatorVersion + + +class TestEvaluatorsUpload: + """Unit tests for EvaluatorsOperations.upload() method.""" + + def _create_operations(self): + """Create a mock EvaluatorsOperations instance with mocked service calls.""" + ops = object.__new__(EvaluatorsOperations) + ops.pending_upload = MagicMock() + ops.list_versions = MagicMock() + ops.create_version = MagicMock() + return ops + + def _create_temp_folder(self, files=None): + """Create a temporary folder with files for testing. + + :param files: dict of {relative_path: content_bytes} + :return: path to temp folder + """ + tmp_dir = tempfile.mkdtemp() + if files is None: + files = {"evaluator.py": b"class MyEvaluator:\n pass\n"} + for rel_path, content in files.items(): + full_path = os.path.join(tmp_dir, rel_path) + os.makedirs(os.path.dirname(full_path), exist_ok=True) + with open(full_path, "wb") as f: + f.write(content) + return tmp_dir + + def _mock_pending_upload_response(self, blob_uri="https://storage.blob.core.windows.net/container-1"): + """Return a mock pending upload response dict.""" + return { + "blobReferenceForConsumption": { + "blobUri": blob_uri, + "credential": { + "sasUri": f"{blob_uri}?sv=2025-01-05&sig=fakesig", + }, + } + } + + # --------------------------------------------------------------- + # upload() - input validation tests + # --------------------------------------------------------------- + + def test_upload_raises_if_folder_does_not_exist(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + + with pytest.raises(ValueError, match="does not exist"): + ops.upload( + name="test_evaluator", + evaluator_version={"definition": {}}, + folder="/nonexistent/path/abc123", + ) + + def test_upload_raises_if_path_is_file(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + + with tempfile.NamedTemporaryFile(suffix=".py") as tmp: + with pytest.raises(ValueError, match="file, not a folder"): + ops.upload( + name="test_evaluator", + evaluator_version={"definition": {}}, + folder=tmp.name, + ) + + def test_upload_raises_if_folder_is_empty(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + + empty_dir = tempfile.mkdtemp() + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + with pytest.raises(ValueError, match="folder is empty"): + ops.upload( + name="test_evaluator", + evaluator_version={"definition": {}}, + folder=empty_dir, + ) + + # --------------------------------------------------------------- + # upload() - version auto-increment tests + # --------------------------------------------------------------- + + def test_get_next_version_returns_1_for_new_evaluator(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + assert ops._get_next_version("new_evaluator") == "1" + + def test_get_next_version_returns_1_for_empty_list(self): + ops = self._create_operations() + ops.list_versions.return_value = [] + assert ops._get_next_version("empty_evaluator") == "1" + + def test_get_next_version_increments_highest_version(self): + ops = self._create_operations() + ops.list_versions.return_value = [ + {"version": "1"}, + {"version": "3"}, + {"version": "2"}, + ] + assert ops._get_next_version("existing_evaluator") == "4" + + def test_get_next_version_ignores_non_numeric_versions(self): + ops = self._create_operations() + ops.list_versions.return_value = [ + {"version": "1"}, + {"version": "latest"}, + {"version": "beta"}, + ] + assert ops._get_next_version("mixed_evaluator") == "2" + + # --------------------------------------------------------------- + # upload() - pending upload / SAS URI validation tests + # --------------------------------------------------------------- + + def test_start_pending_upload_raises_if_no_blob_ref(self): + ops = self._create_operations() + ops.pending_upload.return_value = {} + + with pytest.raises(ValueError, match="Blob reference is not present"): + ops._start_pending_upload_and_get_container_client("test", "1") + + def test_start_pending_upload_raises_if_no_credential(self): + ops = self._create_operations() + ops.pending_upload.return_value = { + "blobReferenceForConsumption": { + "blobUri": "https://storage.blob.core.windows.net/container", + } + } + + with pytest.raises(ValueError, match="SAS credential is not present"): + ops._start_pending_upload_and_get_container_client("test", "1") + + def test_start_pending_upload_raises_if_no_sas_uri(self): + ops = self._create_operations() + ops.pending_upload.return_value = { + "blobReferenceForConsumption": { + "blobUri": "https://storage.blob.core.windows.net/container", + "credential": {"type": "SAS"}, + } + } + + with pytest.raises(ValueError, match="SAS URI is missing"): + ops._start_pending_upload_and_get_container_client("test", "1") + + def test_start_pending_upload_raises_if_no_blob_uri(self): + ops = self._create_operations() + ops.pending_upload.return_value = { + "blobReferenceForConsumption": { + "credential": { + "sasUri": "https://storage.blob.core.windows.net/container?sig=fake", + }, + } + } + + with pytest.raises(ValueError, match="Blob URI is missing"): + ops._start_pending_upload_and_get_container_client("test", "1") + + def test_start_pending_upload_passes_connection_name(self): + ops = self._create_operations() + ops.pending_upload.return_value = self._mock_pending_upload_response() + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient"): + ops._start_pending_upload_and_get_container_client("test", "1", connection_name="my-connection") + + ops.pending_upload.assert_called_once_with( + name="test", + version="1", + pending_upload_request={"connectionName": "my-connection"}, + ) + + # --------------------------------------------------------------- + # upload() - file upload behavior tests + # --------------------------------------------------------------- + + def test_upload_uploads_single_file(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder({"evaluator.py": b"class Eval: pass"}) + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + mock_container.upload_blob.assert_called_once() + blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[1].get("name") + assert blob_name == "evaluator.py" + + def test_upload_handles_nested_folders(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder({ + "evaluator.py": b"class Eval: pass", + "utils/__init__.py": b"", + "utils/helper.py": b"def helper(): pass", + }) + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + assert mock_container.upload_blob.call_count == 3 + uploaded_names = sorted( + c.kwargs.get("name") or c[1].get("name") + for c in mock_container.upload_blob.call_args_list + ) + assert uploaded_names == sorted(["evaluator.py", "utils/__init__.py", "utils/helper.py"]) + + def test_upload_skips_pycache_and_pyc_files(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder({ + "evaluator.py": b"class Eval: pass", + "__pycache__/evaluator.cpython-312.pyc": b"compiled", + "other.pyc": b"compiled", + "other.pyo": b"optimized", + }) + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + # Only evaluator.py should be uploaded + assert mock_container.upload_blob.call_count == 1 + blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[1].get("name") + assert blob_name == "evaluator.py" + + # --------------------------------------------------------------- + # upload() - blob_uri set on evaluator version tests + # --------------------------------------------------------------- + + def test_upload_sets_blob_uri_on_dict_evaluator_version(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + blob_uri = "https://storage.blob.core.windows.net/container-1" + ops.pending_upload.return_value = self._mock_pending_upload_response(blob_uri=blob_uri) + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder() + + evaluator_version = {"definition": {"entry_point": "eval:Eval"}} + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + ops.upload( + name="test", + evaluator_version=evaluator_version, + folder=folder, + ) + + # Verify blob_uri was set in the definition + assert evaluator_version["definition"]["blob_uri"] == blob_uri + + def test_upload_sets_blob_uri_on_model_evaluator_version(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + blob_uri = "https://storage.blob.core.windows.net/container-1" + ops.pending_upload.return_value = self._mock_pending_upload_response(blob_uri=blob_uri) + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder() + + # Create a mock EvaluatorVersion object + ev = MagicMock(spec=EvaluatorVersion) + ev.definition = MagicMock() + ev.definition.blob_uri = None + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + ops.upload( + name="test", + evaluator_version=ev, + folder=folder, + ) + + # Verify blob_uri was set on the model object + assert ev.definition.blob_uri == blob_uri + + # --------------------------------------------------------------- + # upload() - create_version call tests + # --------------------------------------------------------------- + + def test_upload_calls_create_version_with_correct_args(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "my_eval", "version": "1"} + + folder = self._create_temp_folder() + evaluator_version = {"definition": {"entry_point": "eval:Eval"}} + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + result = ops.upload( + name="my_eval", + evaluator_version=evaluator_version, + folder=folder, + ) + + ops.create_version.assert_called_once_with( + name="my_eval", + evaluator_version=evaluator_version, + ) + assert result == {"name": "my_eval", "version": "1"} + + def test_upload_auto_increments_version(self): + ops = self._create_operations() + ops.list_versions.return_value = [{"version": "1"}, {"version": "2"}] + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "my_eval", "version": "3"} + + folder = self._create_temp_folder() + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + ops.upload( + name="my_eval", + evaluator_version={"definition": {}}, + folder=folder, + ) + + # pending_upload should be called with version "3" + ops.pending_upload.assert_called_once_with( + name="my_eval", + version="3", + pending_upload_request={}, + ) + + # --------------------------------------------------------------- + # upload() - error handling tests + # --------------------------------------------------------------- + + def test_upload_raises_permission_error_on_auth_mismatch(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + + folder = self._create_temp_folder() + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + mock_container.url = "https://mystorage.blob.core.windows.net/container" + + error = HttpResponseError(message="Auth failed") + error.error_code = "AuthorizationPermissionMismatch" + error.response = MagicMock() + mock_container.upload_blob.side_effect = error + + with pytest.raises(HttpResponseError, match="Storage Blob Data Contributor"): + ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + def test_upload_reraises_non_auth_http_errors(self): + ops = self._create_operations() + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + + folder = self._create_temp_folder() + + with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: + mock_container = MagicMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__enter__ = MagicMock(return_value=mock_container) + mock_container.__exit__ = MagicMock(return_value=False) + + error = HttpResponseError(message="Server error") + error.error_code = "InternalServerError" + mock_container.upload_blob.side_effect = error + + with pytest.raises(HttpResponseError, match="Server error"): + ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py new file mode 100644 index 000000000000..c37d9f891e9a --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py @@ -0,0 +1,497 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import os +import tempfile +import pytest +from unittest.mock import AsyncMock, MagicMock, patch +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError +from azure.ai.projects.aio.operations._patch_evaluators_async import EvaluatorsOperations +from azure.ai.projects.models import EvaluatorVersion + + +class TestEvaluatorsUploadAsync: + """Unit tests for async EvaluatorsOperations.upload() method.""" + + def _create_operations(self): + """Create a mock async EvaluatorsOperations instance with mocked service calls.""" + ops = object.__new__(EvaluatorsOperations) + ops.pending_upload = AsyncMock() + ops.list_versions = MagicMock() + ops.create_version = AsyncMock() + return ops + + def _create_temp_folder(self, files=None): + """Create a temporary folder with files for testing. + + :param files: dict of {relative_path: content_bytes} + :return: path to temp folder + """ + tmp_dir = tempfile.mkdtemp() + if files is None: + files = {"evaluator.py": b"class MyEvaluator:\n pass\n"} + for rel_path, content in files.items(): + full_path = os.path.join(tmp_dir, rel_path) + os.makedirs(os.path.dirname(full_path), exist_ok=True) + with open(full_path, "wb") as f: + f.write(content) + return tmp_dir + + def _mock_pending_upload_response(self, blob_uri="https://storage.blob.core.windows.net/container-1"): + """Return a mock pending upload response dict.""" + return { + "blobReferenceForConsumption": { + "blobUri": blob_uri, + "credential": { + "sasUri": f"{blob_uri}?sv=2025-01-05&sig=fakesig", + }, + } + } + + # --------------------------------------------------------------- + # upload() - input validation tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_upload_raises_if_folder_does_not_exist(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + + with pytest.raises(ValueError, match="does not exist"): + await ops.upload( + name="test_evaluator", + evaluator_version={"definition": {}}, + folder="/nonexistent/path/abc123", + ) + + @pytest.mark.asyncio + async def test_upload_raises_if_path_is_file(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + + with tempfile.NamedTemporaryFile(suffix=".py") as tmp: + with pytest.raises(ValueError, match="file, not a folder"): + await ops.upload( + name="test_evaluator", + evaluator_version={"definition": {}}, + folder=tmp.name, + ) + + @pytest.mark.asyncio + async def test_upload_raises_if_folder_is_empty(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + + empty_dir = tempfile.mkdtemp() + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + with pytest.raises(ValueError, match="folder is empty"): + await ops.upload( + name="test_evaluator", + evaluator_version={"definition": {}}, + folder=empty_dir, + ) + + # --------------------------------------------------------------- + # upload() - version auto-increment tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_get_next_version_returns_1_for_new_evaluator(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + assert await ops._get_next_version("new_evaluator") == "1" + + @pytest.mark.asyncio + async def test_get_next_version_returns_1_for_empty_list(self): + ops = self._create_operations() + + async def _empty_gen(*args, **kwargs): + return + yield # noqa: make it an async generator + + ops.list_versions.return_value = _empty_gen() + assert await ops._get_next_version("empty_evaluator") == "1" + + @pytest.mark.asyncio + async def test_get_next_version_increments_highest_version(self): + ops = self._create_operations() + + async def _version_gen(*args, **kwargs): + for v in [{"version": "1"}, {"version": "3"}, {"version": "2"}]: + yield v + + ops.list_versions.return_value = _version_gen() + assert await ops._get_next_version("existing_evaluator") == "4" + + @pytest.mark.asyncio + async def test_get_next_version_ignores_non_numeric_versions(self): + ops = self._create_operations() + + async def _version_gen(*args, **kwargs): + for v in [{"version": "1"}, {"version": "latest"}, {"version": "beta"}]: + yield v + + ops.list_versions.return_value = _version_gen() + assert await ops._get_next_version("mixed_evaluator") == "2" + + # --------------------------------------------------------------- + # upload() - pending upload / SAS URI validation tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_start_pending_upload_raises_if_no_blob_ref(self): + ops = self._create_operations() + ops.pending_upload.return_value = {} + + with pytest.raises(ValueError, match="Blob reference is not present"): + await ops._start_pending_upload_and_get_container_client("test", "1") + + @pytest.mark.asyncio + async def test_start_pending_upload_raises_if_no_credential(self): + ops = self._create_operations() + ops.pending_upload.return_value = { + "blobReferenceForConsumption": { + "blobUri": "https://storage.blob.core.windows.net/container", + } + } + + with pytest.raises(ValueError, match="SAS credential is not present"): + await ops._start_pending_upload_and_get_container_client("test", "1") + + @pytest.mark.asyncio + async def test_start_pending_upload_raises_if_no_sas_uri(self): + ops = self._create_operations() + ops.pending_upload.return_value = { + "blobReferenceForConsumption": { + "blobUri": "https://storage.blob.core.windows.net/container", + "credential": {"type": "SAS"}, + } + } + + with pytest.raises(ValueError, match="SAS URI is missing"): + await ops._start_pending_upload_and_get_container_client("test", "1") + + @pytest.mark.asyncio + async def test_start_pending_upload_raises_if_no_blob_uri(self): + ops = self._create_operations() + ops.pending_upload.return_value = { + "blobReferenceForConsumption": { + "credential": { + "sasUri": "https://storage.blob.core.windows.net/container?sig=fake", + }, + } + } + + with pytest.raises(ValueError, match="Blob URI is missing"): + await ops._start_pending_upload_and_get_container_client("test", "1") + + @pytest.mark.asyncio + async def test_start_pending_upload_passes_connection_name(self): + ops = self._create_operations() + ops.pending_upload.return_value = self._mock_pending_upload_response() + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient"): + await ops._start_pending_upload_and_get_container_client("test", "1", connection_name="my-connection") + + ops.pending_upload.assert_called_once_with( + name="test", + version="1", + pending_upload_request={"connectionName": "my-connection"}, + ) + + # --------------------------------------------------------------- + # upload() - file upload behavior tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_upload_uploads_single_file(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder({"evaluator.py": b"class Eval: pass"}) + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + await ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + mock_container.upload_blob.assert_called_once() + blob_name = mock_container.upload_blob.call_args.kwargs.get( + "name" + ) or mock_container.upload_blob.call_args[1].get("name") + assert blob_name == "evaluator.py" + + @pytest.mark.asyncio + async def test_upload_handles_nested_folders(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder( + { + "evaluator.py": b"class Eval: pass", + "utils/__init__.py": b"", + "utils/helper.py": b"def helper(): pass", + } + ) + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + await ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + assert mock_container.upload_blob.call_count == 3 + uploaded_names = sorted( + c.kwargs.get("name") or c[1].get("name") for c in mock_container.upload_blob.call_args_list + ) + assert uploaded_names == sorted(["evaluator.py", "utils/__init__.py", "utils/helper.py"]) + + @pytest.mark.asyncio + async def test_upload_skips_pycache_and_pyc_files(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder( + { + "evaluator.py": b"class Eval: pass", + "__pycache__/evaluator.cpython-312.pyc": b"compiled", + "other.pyc": b"compiled", + "other.pyo": b"optimized", + } + ) + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + await ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + assert mock_container.upload_blob.call_count == 1 + blob_name = mock_container.upload_blob.call_args.kwargs.get( + "name" + ) or mock_container.upload_blob.call_args[1].get("name") + assert blob_name == "evaluator.py" + + # --------------------------------------------------------------- + # upload() - blob_uri set on evaluator version tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_upload_sets_blob_uri_on_dict_evaluator_version(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + blob_uri = "https://storage.blob.core.windows.net/container-1" + ops.pending_upload.return_value = self._mock_pending_upload_response(blob_uri=blob_uri) + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder() + + evaluator_version = {"definition": {"entry_point": "eval:Eval"}} + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + await ops.upload( + name="test", + evaluator_version=evaluator_version, + folder=folder, + ) + + assert evaluator_version["definition"]["blob_uri"] == blob_uri + + @pytest.mark.asyncio + async def test_upload_sets_blob_uri_on_model_evaluator_version(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + blob_uri = "https://storage.blob.core.windows.net/container-1" + ops.pending_upload.return_value = self._mock_pending_upload_response(blob_uri=blob_uri) + ops.create_version.return_value = {"name": "test", "version": "1"} + + folder = self._create_temp_folder() + + ev = MagicMock(spec=EvaluatorVersion) + ev.definition = MagicMock() + ev.definition.blob_uri = None + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + await ops.upload( + name="test", + evaluator_version=ev, + folder=folder, + ) + + assert ev.definition.blob_uri == blob_uri + + # --------------------------------------------------------------- + # upload() - create_version call tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_upload_calls_create_version_with_correct_args(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "my_eval", "version": "1"} + + folder = self._create_temp_folder() + evaluator_version = {"definition": {"entry_point": "eval:Eval"}} + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + result = await ops.upload( + name="my_eval", + evaluator_version=evaluator_version, + folder=folder, + ) + + ops.create_version.assert_called_once_with( + name="my_eval", + evaluator_version=evaluator_version, + ) + assert result == {"name": "my_eval", "version": "1"} + + @pytest.mark.asyncio + async def test_upload_auto_increments_version(self): + ops = self._create_operations() + + async def _version_gen(*args, **kwargs): + for v in [{"version": "1"}, {"version": "2"}]: + yield v + + ops.list_versions.return_value = _version_gen() + ops.pending_upload.return_value = self._mock_pending_upload_response() + ops.create_version.return_value = {"name": "my_eval", "version": "3"} + + folder = self._create_temp_folder() + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + await ops.upload( + name="my_eval", + evaluator_version={"definition": {}}, + folder=folder, + ) + + ops.pending_upload.assert_called_once_with( + name="my_eval", + version="3", + pending_upload_request={}, + ) + + # --------------------------------------------------------------- + # upload() - error handling tests + # --------------------------------------------------------------- + + @pytest.mark.asyncio + async def test_upload_raises_permission_error_on_auth_mismatch(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + + folder = self._create_temp_folder() + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + mock_container.url = "https://mystorage.blob.core.windows.net/container" + + error = HttpResponseError(message="Auth failed") + error.error_code = "AuthorizationPermissionMismatch" + error.response = MagicMock() + mock_container.upload_blob.side_effect = error + + with pytest.raises(HttpResponseError, match="Storage Blob Data Contributor"): + await ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) + + @pytest.mark.asyncio + async def test_upload_reraises_non_auth_http_errors(self): + ops = self._create_operations() + + ops.list_versions.side_effect = ResourceNotFoundError("Not found") + ops.pending_upload.return_value = self._mock_pending_upload_response() + + folder = self._create_temp_folder() + + with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: + mock_container = AsyncMock() + MockContainerClient.from_container_url.return_value = mock_container + mock_container.__aenter__ = AsyncMock(return_value=mock_container) + mock_container.__aexit__ = AsyncMock(return_value=False) + + error = HttpResponseError(message="Server error") + error.error_code = "InternalServerError" + mock_container.upload_blob.side_effect = error + + with pytest.raises(HttpResponseError, match="Server error"): + await ops.upload( + name="test", + evaluator_version={"definition": {}}, + folder=folder, + ) From 2ad4c44bd24dc12b3e5729c3980bfa56c9fd2d41 Mon Sep 17 00:00:00 2001 From: Howie Leung Date: Mon, 23 Mar 2026 11:59:58 -0700 Subject: [PATCH 25/36] added toolset samples and more (#45832) * added toolset samples and more * improved samples * resolved comment --- sdk/ai/azure-ai-projects/CHANGELOG.md | 1 + sdk/ai/azure-ai-projects/assets.json | 2 +- .../samples/toolsets/sample_toolsets_crud.py | 89 +++++++++++++++++ .../toolsets/sample_toolsets_crud_async.py | 97 +++++++++++++++++++ sdk/ai/azure-ai-projects/tests/test_base.py | 2 +- 5 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud.py create mode 100644 sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud_async.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index 32b494ab610b..b254a155fd3d 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -25,6 +25,7 @@ * Added CSV evaluation sample (`sample_evaluations_builtin_with_csv.py`) demonstrating evaluation with an uploaded CSV dataset. * Added synthetic data evaluation samples (`sample_synthetic_data_agent_evaluation.py`) and (`sample_synthetic_data_model_evaluation.py`). * Added Chat Completions basic samples (`sample_chat_completions_basic.py`, `sample_chat_completions_basic_async.py`) demonstrating chat completions calls using `AIProjectClient` + the OpenAI-compatible client. +* Added Toolsets CRUD samples (`sample_toolsets_crud.py`, `sample_toolsets_crud_async.py`) demonstrating `project_client.beta.toolsets` create/get/update/list/delete. ### Other Changes diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 05c02f4d58b3..637665dd9510 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_23b51dc081" + "Tag": "python/ai/azure-ai-projects_be6fbb831a" } diff --git a/sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud.py b/sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud.py new file mode 100644 index 000000000000..f08163ee80f0 --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud.py @@ -0,0 +1,89 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to perform CRUD operations on Toolsets + using the synchronous AIProjectClient. + + Toolsets are currently a preview feature. In the Python SDK, you access + these operations via `project_client.beta.toolsets`. + +USAGE: + python sample_toolsets_crud.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.2" python-dotenv + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. +""" + +import os + +from dotenv import load_dotenv + +from azure.core.exceptions import ResourceNotFoundError +from azure.identity import DefaultAzureCredential + +from azure.ai.projects import AIProjectClient +from azure.ai.projects.models import MCPTool, Tool + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + +with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, +): + + toolset_name = "mcp" + + try: + project_client.beta.toolsets.delete(toolset_name) + print(f"Toolset `{toolset_name}` deleted") + except ResourceNotFoundError: + pass + + tools: list[Tool] = [ + MCPTool( + server_label="api_specs", + server_url="https://gitmcp.io/Azure/azure-rest-api-specs", + require_approval="never", + ) + ] + + created = project_client.beta.toolsets.create( + name=toolset_name, + description="Example toolset created by the azure-ai-projects sample.", + metadata={"status": "created"}, + tools=tools, + ) + status = created.metadata.get("status", "unknown status") if created.metadata else "unknown status" + print(f"Toolset: {created.name} (tools: {len(created.tools)}) (status: {status})") + + fetched = project_client.beta.toolsets.get(toolset_name) + print(f"Retrieved toolset: {fetched.name} ({fetched.id})") + + updated = project_client.beta.toolsets.update( + toolset_name, + description="Updated description for the sample toolset.", + metadata={"status": "updated"}, + tools=tools, + ) + status = updated.metadata.get("status", "unknown status") if updated.metadata else "unknown status" + print(f"Toolset: {updated.name} (tools: {len(updated.tools)}) (status: {status})") + + toolsets = list(project_client.beta.toolsets.list(limit=10)) + print(f"Found {len(toolsets)} toolsets") + for item in toolsets: + print(f" - {item.name} ({item.id})") + + project_client.beta.toolsets.delete(toolset_name) + print("Toolset deleted") diff --git a/sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud_async.py b/sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud_async.py new file mode 100644 index 000000000000..a7e12604933f --- /dev/null +++ b/sdk/ai/azure-ai-projects/samples/toolsets/sample_toolsets_crud_async.py @@ -0,0 +1,97 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ + +""" +DESCRIPTION: + This sample demonstrates how to perform CRUD operations on Toolsets + using the asynchronous AIProjectClient. + + Toolsets are currently a preview feature. In the Python SDK, you access + these operations via `project_client.beta.toolsets`. + +USAGE: + python sample_toolsets_crud_async.py + + Before running the sample: + + pip install "azure-ai-projects>=2.0.2" python-dotenv aiohttp + + Set these environment variables with your own values: + 1) FOUNDRY_PROJECT_ENDPOINT - The Azure AI Project endpoint, as found in the Overview + page of your Microsoft Foundry portal. +""" + +import asyncio +import os + +from dotenv import load_dotenv + +from azure.core.exceptions import ResourceNotFoundError +from azure.identity.aio import DefaultAzureCredential + +from azure.ai.projects.aio import AIProjectClient +from azure.ai.projects.models import MCPTool, Tool + +load_dotenv() + +endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"] + + +async def main() -> None: + + async with ( + DefaultAzureCredential() as credential, + AIProjectClient(endpoint=endpoint, credential=credential) as project_client, + ): + + toolset_name = "mcp" + + try: + await project_client.beta.toolsets.delete(toolset_name) + print(f"Toolset `{toolset_name}` deleted") + except ResourceNotFoundError: + pass + + tools: list[Tool] = [ + MCPTool( + server_label="api_specs", + server_url="https://gitmcp.io/Azure/azure-rest-api-specs", + require_approval="never", + ) + ] + + created = await project_client.beta.toolsets.create( + name=toolset_name, + description="Example toolset created by the azure-ai-projects sample.", + metadata={"status": "created"}, + tools=tools, + ) + status = created.metadata.get("status", "unknown status") if created.metadata else "unknown status" + print(f"Toolset: {created.name} (tools: {len(created.tools)}) (status: {status})") + + fetched = await project_client.beta.toolsets.get(toolset_name) + print(f"Retrieved toolset: {fetched.name} ({fetched.id})") + + updated = await project_client.beta.toolsets.update( + toolset_name, + description="Updated description for the sample toolset.", + metadata={"status": "updated"}, + tools=tools, + ) + status = updated.metadata.get("status", "unknown status") if updated.metadata else "unknown status" + print(f"Toolset: {updated.name} (tools: {len(updated.tools)}) (status: {status})") + + toolsets: list[str] = [] + async for item in project_client.beta.toolsets.list(limit=10): + toolsets.append(item.name) + print(f"Found {len(toolsets)} toolsets") + + await project_client.beta.toolsets.delete(toolset_name) + print("Toolset deleted") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index 28fabbc6308a..6069c891faba 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -69,7 +69,7 @@ fabric_user_input="List all customers!", a2a_user_input="What can the secondary agent do?", bing_custom_user_input="Tell me more about foundry agent service", - memory_store_chat_model_deployment_name="sanitized-gpt-memory", + memory_store_chat_model_deployment_name="sanitized-model-deployment-name", memory_store_embedding_model_deployment_name="text-embedding-ada-002", ) From efd85704ec1111310c84c698f2253cc214830bd8 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:05:58 -0700 Subject: [PATCH 26/36] Fix failing unit-tests "test_foundry_features_header" and failing quality gates (#45854) Additional Pyright and Pylint errors will need to be fixed in another PR. --- .../aio/operations/_patch_evaluators_async.py | 3 +- .../projects/operations/_patch_evaluators.py | 3 +- sdk/ai/azure-ai-projects/cspell.json | 5 +- .../answer_length_evaluator.py | 5 +- .../friendly_evaluator/friendly_evaluator.py | 2 + .../telemetry/test_ai_agents_instrumentor.py | 53 ++++++++++--------- .../evaluators/test_evaluators_upload.py | 37 +++++++------ .../test_evaluators_upload_async.py | 12 ++--- .../foundry_features_header_test_base.py | 6 ++- .../tests/samples/test_samples_evaluations.py | 2 + 10 files changed, 74 insertions(+), 54 deletions(-) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py index 2a0bf8dc5829..f1eb84a657a8 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -7,6 +7,7 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ + import os import logging from typing import Any, IO, Tuple, Optional, Union @@ -164,7 +165,7 @@ async def upload( try: await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) except HttpResponseError as e: - if e.error_code == "AuthorizationPermissionMismatch": + if hasattr(e, "error_code") and e.error_code == "AuthorizationPermissionMismatch": storage_account = urlsplit(container_client.url).hostname raise HttpResponseError( message=( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py index 73f3904a8272..1b8b84257df1 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -7,6 +7,7 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ + import os import logging from typing import Any, IO, Tuple, Optional, Union @@ -162,7 +163,7 @@ def upload( try: container_client.upload_blob(name=str(blob_name), data=data, **kwargs) except HttpResponseError as e: - if e.error_code == "AuthorizationPermissionMismatch": + if hasattr(e, "error_code") and e.error_code == "AuthorizationPermissionMismatch": storage_account = urlsplit(container_client.url).hostname raise HttpResponseError( message=( diff --git a/sdk/ai/azure-ai-projects/cspell.json b/sdk/ai/azure-ai-projects/cspell.json index fd24396e3ff0..7d6f3924f9c3 100644 --- a/sdk/ai/azure-ai-projects/cspell.json +++ b/sdk/ai/azure-ai-projects/cspell.json @@ -19,17 +19,18 @@ "ftchkpt", "ftjob", "GENAI", + "genexpr", "getconnectionwithcredentials", "GLEU", "inpainting", "Ministral", "mpkjc", "quantitive", + "reraises", "Tadmaq", "Udbk", "UPIA", - "xhigh", - "genexpr" + "xhigh" ], "ignorePaths": [ "*.csv", diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py index 1fa95ab19b1d..964b7ef1c4d0 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/answer_length_evaluator/answer_length_evaluator.py @@ -9,6 +9,5 @@ def __call__(self, *args, **kwargs): return {"result": evaluate_answer_length(kwargs.get("response"))} -def evaluate_answer_length(answer: str): - return len(answer) - +def evaluate_answer_length(answer: str | None): + return len(answer) if answer else 0 diff --git a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py index c58ff350ba25..2e576855ef09 100644 --- a/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py +++ b/sdk/ai/azure-ai-projects/samples/evaluations/custom_evaluators/friendly_evaluator/friendly_evaluator.py @@ -59,4 +59,6 @@ def __call__(self, *, query: str, response: str, **kwargs) -> dict: ) raw_result = completion.choices[0].message.content + if raw_result is None: + raise ValueError("No content in completion response") return parse_evaluation_result(raw_result) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py index f60fa074c624..cbee3236f66e 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_ai_agents_instrumentor.py @@ -189,19 +189,19 @@ def test_experimental_genai_tracing_gate(self, env_value: Optional[str], should_ "env_value, instrument_kwarg, expected_propagated, baggage_env_value, baggage_kwarg, expected_baggage", [ # --- trace context propagation (baggage defaults off) --- - (None, None, True, None, None, False), # default: traceparent on, baggage off - ("true", None, True, None, None, False), # explicit true - ("True", None, True, None, None, False), # case-insensitive - ("TRUE", None, True, None, None, False), # case-insensitive - ("false", None, False, None, None, False), # propagation off → no headers at all - ("False", None, False, None, None, False), # case-insensitive - (None, True, True, None, None, False), # parameter override: True - (None, False, False, None, None, False), # parameter override: False + (None, None, True, None, None, False), # default: traceparent on, baggage off + ("true", None, True, None, None, False), # explicit true + ("True", None, True, None, None, False), # case-insensitive + ("TRUE", None, True, None, None, False), # case-insensitive + ("false", None, False, None, None, False), # propagation off → no headers at all + ("False", None, False, None, None, False), # case-insensitive + (None, True, True, None, None, False), # parameter override: True + (None, False, False, None, None, False), # parameter override: False # --- baggage propagation (trace context on via default) --- - (None, None, True, "true", None, True), # baggage env=true → baggage header present - (None, None, True, "false", None, False), # baggage env=false → baggage header absent - (None, None, True, None, True, True), # baggage kwarg=True → baggage header present - (None, None, True, None, False, False), # baggage kwarg=False → baggage header absent + (None, None, True, "true", None, True), # baggage env=true → baggage header present + (None, None, True, "false", None, False), # baggage env=false → baggage header absent + (None, None, True, None, True, True), # baggage kwarg=True → baggage header present + (None, None, True, None, False, False), # baggage kwarg=False → baggage header absent # baggage enabled but trace propagation disabled → hook not installed, baggage still absent (None, False, False, None, True, False), ], @@ -279,7 +279,9 @@ def test_trace_context_propagation( import azure.ai.projects.telemetry._ai_project_instrumentor as _instrumentor_mod # Verify the module-level global for trace context propagation reflects the correct state. - assert _instrumentor_mod._trace_context_propagation_enabled == expected_propagated, ( # pylint: disable=protected-access + assert ( + _instrumentor_mod._trace_context_propagation_enabled == expected_propagated + ), ( # pylint: disable=protected-access f"Expected _trace_context_propagation_enabled={expected_propagated} " f"for env={env_value!r} / kwarg={instrument_kwarg!r}" ) @@ -315,6 +317,7 @@ def fake_factory(*args, **kwargs): # on top of the current global context, so baggage set only in that context arg # would be silently dropped. from opentelemetry import context as otel_context + tracer = trace.get_tracer(__name__) ctx_with_baggage = otel_baggage.set_baggage("test-key", "test-value") token = otel_context.attach(ctx_with_baggage) @@ -328,22 +331,22 @@ def fake_factory(*args, **kwargs): header_names = [h.lower() for h in transport.last_request.headers.keys()] if expected_propagated: - assert "traceparent" in header_names, ( - f"Expected traceparent header to be present (env={env_value!r}, kwarg={instrument_kwarg!r})" - ) + assert ( + "traceparent" in header_names + ), f"Expected traceparent header to be present (env={env_value!r}, kwarg={instrument_kwarg!r})" else: - assert "traceparent" not in header_names, ( - f"Expected traceparent header to be absent (env={env_value!r}, kwarg={instrument_kwarg!r})" - ) + assert ( + "traceparent" not in header_names + ), f"Expected traceparent header to be absent (env={env_value!r}, kwarg={instrument_kwarg!r})" if expected_baggage: - assert "baggage" in header_names, ( - f"Expected baggage header to be present (baggage_env={baggage_env_value!r}, baggage_kwarg={baggage_kwarg!r})" - ) + assert ( + "baggage" in header_names + ), f"Expected baggage header to be present (baggage_env={baggage_env_value!r}, baggage_kwarg={baggage_kwarg!r})" else: - assert "baggage" not in header_names, ( - f"Expected baggage header to be absent (baggage_env={baggage_env_value!r}, baggage_kwarg={baggage_kwarg!r})" - ) + assert ( + "baggage" not in header_names + ), f"Expected baggage header to be absent (baggage_env={baggage_env_value!r}, baggage_kwarg={baggage_kwarg!r})" finally: exporter.shutdown() diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py index a222b1d1db1d..13f2a742d63a 100644 --- a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py @@ -214,7 +214,9 @@ def test_upload_uploads_single_file(self): ) mock_container.upload_blob.assert_called_once() - blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[1].get("name") + blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[ + 1 + ].get("name") assert blob_name == "evaluator.py" def test_upload_handles_nested_folders(self): @@ -223,11 +225,13 @@ def test_upload_handles_nested_folders(self): ops.pending_upload.return_value = self._mock_pending_upload_response() ops.create_version.return_value = {"name": "test", "version": "1"} - folder = self._create_temp_folder({ - "evaluator.py": b"class Eval: pass", - "utils/__init__.py": b"", - "utils/helper.py": b"def helper(): pass", - }) + folder = self._create_temp_folder( + { + "evaluator.py": b"class Eval: pass", + "utils/__init__.py": b"", + "utils/helper.py": b"def helper(): pass", + } + ) with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: mock_container = MagicMock() @@ -243,8 +247,7 @@ def test_upload_handles_nested_folders(self): assert mock_container.upload_blob.call_count == 3 uploaded_names = sorted( - c.kwargs.get("name") or c[1].get("name") - for c in mock_container.upload_blob.call_args_list + c.kwargs.get("name") or c[1].get("name") for c in mock_container.upload_blob.call_args_list ) assert uploaded_names == sorted(["evaluator.py", "utils/__init__.py", "utils/helper.py"]) @@ -254,12 +257,14 @@ def test_upload_skips_pycache_and_pyc_files(self): ops.pending_upload.return_value = self._mock_pending_upload_response() ops.create_version.return_value = {"name": "test", "version": "1"} - folder = self._create_temp_folder({ - "evaluator.py": b"class Eval: pass", - "__pycache__/evaluator.cpython-312.pyc": b"compiled", - "other.pyc": b"compiled", - "other.pyo": b"optimized", - }) + folder = self._create_temp_folder( + { + "evaluator.py": b"class Eval: pass", + "__pycache__/evaluator.cpython-312.pyc": b"compiled", + "other.pyc": b"compiled", + "other.pyo": b"optimized", + } + ) with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: mock_container = MagicMock() @@ -275,7 +280,9 @@ def test_upload_skips_pycache_and_pyc_files(self): # Only evaluator.py should be uploaded assert mock_container.upload_blob.call_count == 1 - blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[1].get("name") + blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[ + 1 + ].get("name") assert blob_name == "evaluator.py" # --------------------------------------------------------------- diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py index c37d9f891e9a..ea3ffe45201d 100644 --- a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py @@ -239,9 +239,9 @@ async def test_upload_uploads_single_file(self): ) mock_container.upload_blob.assert_called_once() - blob_name = mock_container.upload_blob.call_args.kwargs.get( - "name" - ) or mock_container.upload_blob.call_args[1].get("name") + blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[ + 1 + ].get("name") assert blob_name == "evaluator.py" @pytest.mark.asyncio @@ -308,9 +308,9 @@ async def test_upload_skips_pycache_and_pyc_files(self): ) assert mock_container.upload_blob.call_count == 1 - blob_name = mock_container.upload_blob.call_args.kwargs.get( - "name" - ) or mock_container.upload_blob.call_args[1].get("name") + blob_name = mock_container.upload_blob.call_args.kwargs.get("name") or mock_container.upload_blob.call_args[ + 1 + ].get("name") assert blob_name == "evaluator.py" # --------------------------------------------------------------- diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py index efb53002ddf2..802b4ca37dbe 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/foundry_features_header_test_base.py @@ -17,6 +17,7 @@ import inspect import pytest +import tempfile from typing import Any, ClassVar, List, Tuple, Union, get_origin from azure.core.credentials import AccessToken @@ -127,11 +128,14 @@ def _fake_for_param(param: inspect.Parameter) -> Any: Resolution order: - Union types (e.g. Union[Model, JSON, IO[bytes]]) -> {} (JSON body) - list / List[...] -> [] - - str -> "fake-value" + - str -> "fake-value" (special case: for "folder" param we return the OS temp dir, since we need the folder to exist.) - int -> 0 - bool -> False - anything else (model classes, etc.) -> {} """ + if param.name == "folder" and param.annotation is str: + return tempfile.gettempdir() + ann = param.annotation if ann is inspect.Parameter.empty: return "fake-value" diff --git a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py index df5c91e08d91..bb68d1015730 100644 --- a/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py +++ b/sdk/ai/azure-ai-projects/tests/samples/test_samples_evaluations.py @@ -171,6 +171,8 @@ class TestSamplesEvaluations(AzureRecordedTestCase): "sample_synthetic_data_agent_evaluation.py", # Synthetic data gen is long-running preview feature "sample_synthetic_data_model_evaluation.py", # Synthetic data gen is long-running preview feature "sample_eval_catalog_prompt_based_evaluators.py", # For some reason fails with 500 (Internal server error) + "sample_eval_upload_custom_evaluator.py", # TODO: Need to add recordings + "sample_eval_upload_friendly_evaluator.py", # TODO: Need to add recordings ], ), ) From b0358002d25573925429456db2938e4b44c9c8ec Mon Sep 17 00:00:00 2001 From: Waqas Javed <7674577+w-javed@users.noreply.github.com> Date: Tue, 24 Mar 2026 00:04:26 -0700 Subject: [PATCH 27/36] Fix pyright and pylint errors in evaluator upload operations (#45867) * Fix pyright and pylint errors in evaluator upload operations - Replace hasattr(e, 'error_code') with getattr(e, 'error_code', None) to fix pyright reportAttributeAccessIssue on HttpResponseError - Narrow union types with isinstance checks for EvaluatorVersion and CodeBasedEvaluatorDefinition to fix pyright type errors on definition/blob_uri - Extract _upload_folder_to_blob() and _set_blob_uri() helpers to fix pylint R0914 (too many local variables) in upload() - Update tests to use MagicMock(spec=CodeBasedEvaluatorDefinition) for definition mocks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add missing docstring params, return, and rtype for Azure SDK lint rules Fix C4739/C4741/C4742 violations from azure-pylint-guidelines-checker: - _set_blob_uri: add :param and :type for evaluator_version, blob_uri - _start_pending_upload_and_get_container_client: add :param/:type/:return/:rtype - _get_next_version: add :param/:type/:return/:rtype Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add missing param docs for _upload_folder_to_blob (C4739) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../aio/operations/_patch_evaluators_async.py | 152 ++++++++++++------ .../projects/operations/_patch_evaluators.py | 152 ++++++++++++------ .../evaluators/test_evaluators_upload.py | 4 +- .../test_evaluators_upload_async.py | 4 +- 4 files changed, 202 insertions(+), 110 deletions(-) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py index f1eb84a657a8..f0ee18c53178 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -18,6 +18,7 @@ from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from ._operations import BetaEvaluatorsOperations as EvaluatorsOperationsGenerated, JSON from ...models._models import ( + CodeBasedEvaluatorDefinition, EvaluatorVersion, ) @@ -34,13 +35,101 @@ class EvaluatorsOperations(EvaluatorsOperationsGenerated): :attr:`beta.evaluators` attribute. """ + @staticmethod + async def _upload_folder_to_blob( + container_client: ContainerClient, + folder: str, + **kwargs: Any, + ) -> None: + """Walk *folder* and upload every eligible file to the blob container. + + Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` + directories and ``.pyc`` / ``.pyo`` files. + + :param container_client: The blob container client to upload files to. + :type container_client: ~azure.storage.blob.ContainerClient + :param folder: Path to the local folder containing files to upload. + :type folder: str + :raises ValueError: If the folder contains no uploadable files. + :raises HttpResponseError: Re-raised with a friendlier message on + ``AuthorizationPermissionMismatch``. + """ + skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} + skip_extensions = {".pyc", ".pyo"} + files_uploaded = False + + for root, dirs, files in os.walk(folder): + dirs[:] = [d for d in dirs if d not in skip_dirs] + for file_name in files: + if any(file_name.endswith(ext) for ext in skip_extensions): + continue + file_path = os.path.join(root, file_name) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") + logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) + with open(file=file_path, mode="rb") as data: + try: + await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + except HttpResponseError as e: + if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": + storage_account = urlsplit(container_client.url).hostname + raise HttpResponseError( + message=( + f"Failed to upload file '{blob_name}' to blob storage: " + f"permission denied. Ensure the identity that signed the SAS token " + f"has the 'Storage Blob Data Contributor' role on the storage account " + f"'{storage_account}'. " + f"Original error: {e.message}" + ), + response=e.response, + ) from e + raise + logger.debug("[upload] Done uploading file") + files_uploaded = True + + logger.debug("[upload] Done uploading all files.") + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + @staticmethod + def _set_blob_uri( + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + blob_uri: str, + ) -> None: + """Set ``blob_uri`` on the evaluator version's definition. + + :param evaluator_version: The evaluator version object to update. + :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] + :param blob_uri: The blob URI to set on the definition. + :type blob_uri: str + """ + if isinstance(evaluator_version, dict): + definition = evaluator_version.get("definition", {}) + if isinstance(definition, dict): + definition["blob_uri"] = blob_uri + elif isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + elif isinstance(evaluator_version, EvaluatorVersion): + definition = evaluator_version.definition + if isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + async def _start_pending_upload_and_get_container_client( self, name: str, version: str, connection_name: Optional[str] = None, ) -> Tuple[ContainerClient, str, str]: - """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI.""" + """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. + + :param name: The evaluator name. + :type name: str + :param version: The evaluator version. + :type version: str + :param connection_name: Optional storage account connection name. + :type connection_name: Optional[str] + :return: A tuple of (ContainerClient, version, blob_uri). + :rtype: Tuple[ContainerClient, str, str] + """ request_body: dict = {} if connection_name: @@ -76,7 +165,13 @@ async def _start_pending_upload_and_get_container_client( ) async def _get_next_version(self, name: str) -> str: - """Get the next version number for an evaluator by fetching existing versions.""" + """Get the next version number for an evaluator by fetching existing versions. + + :param name: The evaluator name. + :type name: str + :return: The next version number as a string. + :rtype: str + """ try: versions = [] async for v in self.list_versions(name=name): @@ -144,57 +239,8 @@ async def upload( ) async with container_client: - # Upload all files from the folder (including nested subdirectories) - skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} - skip_extensions = {".pyc", ".pyo"} - files_uploaded: bool = False - for root, dirs, files in os.walk(folder): - # Prune directories we don't want to traverse - dirs[:] = [d for d in dirs if d not in skip_dirs] - for file in files: - if any(file.endswith(ext) for ext in skip_extensions): - continue - file_path = os.path.join(root, file) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") - logger.debug( - "[upload] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - with open(file=file_path, mode="rb") as data: - try: - await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - except HttpResponseError as e: - if hasattr(e, "error_code") and e.error_code == "AuthorizationPermissionMismatch": - storage_account = urlsplit(container_client.url).hostname - raise HttpResponseError( - message=( - f"Failed to upload file '{blob_name}' to blob storage: " - f"permission denied. Ensure the identity that signed the SAS token " - f"has the 'Storage Blob Data Contributor' role on the storage account " - f"'{storage_account}'. " - f"Original error: {e.message}" - ), - response=e.response, - ) from e - raise - logger.debug("[upload] Done uploading file") - files_uploaded = True - logger.debug("[upload] Done uploading all files.") - - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - # Set the blob_uri in the evaluator version definition - if isinstance(evaluator_version, dict): - definition = evaluator_version.get("definition", {}) - if isinstance(definition, dict): - definition["blob_uri"] = blob_uri - else: - definition.blob_uri = blob_uri - else: - if hasattr(evaluator_version, "definition") and evaluator_version.definition: - evaluator_version.definition.blob_uri = blob_uri + await self._upload_folder_to_blob(container_client, folder, **kwargs) + self._set_blob_uri(evaluator_version, blob_uri) result = await self.create_version( name=name, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py index 1b8b84257df1..20a218ac6d49 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -18,6 +18,7 @@ from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from ._operations import BetaEvaluatorsOperations as EvaluatorsOperationsGenerated, JSON from ..models._models import ( + CodeBasedEvaluatorDefinition, EvaluatorVersion, ) @@ -34,13 +35,101 @@ class EvaluatorsOperations(EvaluatorsOperationsGenerated): :attr:`beta.evaluators` attribute. """ + @staticmethod + def _upload_folder_to_blob( + container_client: ContainerClient, + folder: str, + **kwargs: Any, + ) -> None: + """Walk *folder* and upload every eligible file to the blob container. + + Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` + directories and ``.pyc`` / ``.pyo`` files. + + :param container_client: The blob container client to upload files to. + :type container_client: ~azure.storage.blob.ContainerClient + :param folder: Path to the local folder containing files to upload. + :type folder: str + :raises ValueError: If the folder contains no uploadable files. + :raises HttpResponseError: Re-raised with a friendlier message on + ``AuthorizationPermissionMismatch``. + """ + skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} + skip_extensions = {".pyc", ".pyo"} + files_uploaded = False + + for root, dirs, files in os.walk(folder): + dirs[:] = [d for d in dirs if d not in skip_dirs] + for file_name in files: + if any(file_name.endswith(ext) for ext in skip_extensions): + continue + file_path = os.path.join(root, file_name) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") + logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) + with open(file=file_path, mode="rb") as data: + try: + container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + except HttpResponseError as e: + if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": + storage_account = urlsplit(container_client.url).hostname + raise HttpResponseError( + message=( + f"Failed to upload file '{blob_name}' to blob storage: " + f"permission denied. Ensure the identity that signed the SAS token " + f"has the 'Storage Blob Data Contributor' role on the storage account " + f"'{storage_account}'. " + f"Original error: {e.message}" + ), + response=e.response, + ) from e + raise + logger.debug("[upload] Done uploading file") + files_uploaded = True + + logger.debug("[upload] Done uploading all files.") + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + @staticmethod + def _set_blob_uri( + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + blob_uri: str, + ) -> None: + """Set ``blob_uri`` on the evaluator version's definition. + + :param evaluator_version: The evaluator version object to update. + :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] + :param blob_uri: The blob URI to set on the definition. + :type blob_uri: str + """ + if isinstance(evaluator_version, dict): + definition = evaluator_version.get("definition", {}) + if isinstance(definition, dict): + definition["blob_uri"] = blob_uri + elif isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + elif isinstance(evaluator_version, EvaluatorVersion): + definition = evaluator_version.definition + if isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + def _start_pending_upload_and_get_container_client( self, name: str, version: str, connection_name: Optional[str] = None, ) -> Tuple[ContainerClient, str, str]: - """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI.""" + """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. + + :param name: The evaluator name. + :type name: str + :param version: The evaluator version. + :type version: str + :param connection_name: Optional storage account connection name. + :type connection_name: Optional[str] + :return: A tuple of (ContainerClient, version, blob_uri). + :rtype: Tuple[ContainerClient, str, str] + """ request_body: dict = {} if connection_name: @@ -76,7 +165,13 @@ def _start_pending_upload_and_get_container_client( ) def _get_next_version(self, name: str) -> str: - """Get the next version number for an evaluator by fetching existing versions.""" + """Get the next version number for an evaluator by fetching existing versions. + + :param name: The evaluator name. + :type name: str + :return: The next version number as a string. + :rtype: str + """ try: versions = list(self.list_versions(name=name)) if versions: @@ -142,57 +237,8 @@ def upload( ) with container_client: - # Upload all files from the folder (including nested subdirectories) - skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} - skip_extensions = {".pyc", ".pyo"} - files_uploaded: bool = False - for root, dirs, files in os.walk(folder): - # Prune directories we don't want to traverse - dirs[:] = [d for d in dirs if d not in skip_dirs] - for file in files: - if any(file.endswith(ext) for ext in skip_extensions): - continue - file_path = os.path.join(root, file) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") - logger.debug( - "[upload] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - with open(file=file_path, mode="rb") as data: - try: - container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - except HttpResponseError as e: - if hasattr(e, "error_code") and e.error_code == "AuthorizationPermissionMismatch": - storage_account = urlsplit(container_client.url).hostname - raise HttpResponseError( - message=( - f"Failed to upload file '{blob_name}' to blob storage: " - f"permission denied. Ensure the identity that signed the SAS token " - f"has the 'Storage Blob Data Contributor' role on the storage account " - f"'{storage_account}'. " - f"Original error: {e.message}" - ), - response=e.response, - ) from e - raise - logger.debug("[upload] Done uploading file") - files_uploaded = True - logger.debug("[upload] Done uploading all files.") - - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - # Set the blob_uri in the evaluator version definition - if isinstance(evaluator_version, dict): - definition = evaluator_version.get("definition", {}) - if isinstance(definition, dict): - definition["blob_uri"] = blob_uri - else: - definition.blob_uri = blob_uri - else: - if hasattr(evaluator_version, "definition") and evaluator_version.definition: - evaluator_version.definition.blob_uri = blob_uri + self._upload_folder_to_blob(container_client, folder, **kwargs) + self._set_blob_uri(evaluator_version, blob_uri) result = self.create_version( name=name, diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py index 13f2a742d63a..84c243fc0fa8 100644 --- a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py @@ -9,7 +9,7 @@ from unittest.mock import MagicMock, patch, call from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from azure.ai.projects.operations._patch_evaluators import EvaluatorsOperations -from azure.ai.projects.models import EvaluatorVersion +from azure.ai.projects.models import CodeBasedEvaluatorDefinition, EvaluatorVersion class TestEvaluatorsUpload: @@ -326,7 +326,7 @@ def test_upload_sets_blob_uri_on_model_evaluator_version(self): # Create a mock EvaluatorVersion object ev = MagicMock(spec=EvaluatorVersion) - ev.definition = MagicMock() + ev.definition = MagicMock(spec=CodeBasedEvaluatorDefinition) ev.definition.blob_uri = None with patch("azure.ai.projects.operations._patch_evaluators.ContainerClient") as MockContainerClient: diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py index ea3ffe45201d..ff85c791b2f2 100644 --- a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py @@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, MagicMock, patch from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from azure.ai.projects.aio.operations._patch_evaluators_async import EvaluatorsOperations -from azure.ai.projects.models import EvaluatorVersion +from azure.ai.projects.models import CodeBasedEvaluatorDefinition, EvaluatorVersion class TestEvaluatorsUploadAsync: @@ -356,7 +356,7 @@ async def test_upload_sets_blob_uri_on_model_evaluator_version(self): folder = self._create_temp_folder() ev = MagicMock(spec=EvaluatorVersion) - ev.definition = MagicMock() + ev.definition = MagicMock(spec=CodeBasedEvaluatorDefinition) ev.definition.blob_uri = None with patch("azure.ai.projects.aio.operations._patch_evaluators_async.ContainerClient") as MockContainerClient: From d948b5fb17229da6c2ba8ff16652cf11cdd9d5d5 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:19:45 -0700 Subject: [PATCH 28/36] A better way to handle removing "foundry_features" input argument from public methods (#45834) --- .../auto_set_foundry_features.py | 568 ------------------ .../auto_set_foundry_features.readme.txt | 128 ---- .../azure-ai-projects/apiview-properties.json | 2 - .../ai/projects/aio/operations/_operations.py | 197 +----- .../ai/projects/aio/operations/_patch.py | 10 +- .../aio/operations/_patch_agents_async.py | 18 +- .../_patch_evaluation_rules_async.py | 15 + .../aio/operations/_patch_evaluators_async.py | 14 +- .../aio/operations/_patch_memories_async.py | 9 +- .../azure/ai/projects/models/_models.py | 2 +- .../azure/ai/projects/models/_patch.py | 32 + .../ai/projects/operations/_operations.py | 413 ++----------- .../azure/ai/projects/operations/_patch.py | 67 ++- .../ai/projects/operations/_patch_agents.py | 21 + .../operations/_patch_evaluation_rules.py | 16 + .../projects/operations/_patch_evaluators.py | 16 +- .../ai/projects/operations/_patch_memories.py | 9 +- .../azure-ai-projects/post-emitter-fixes.cmd | 21 +- sdk/ai/azure-ai-projects/pyproject.toml | 1 - .../evaluators/test_evaluators_upload.py | 14 +- .../test_evaluators_upload_async.py | 17 +- ...dry_features_header_on_beta_operations.py} | 116 +++- ...atures_header_on_beta_operations_async.py} | 129 +++- ...oundry_features_header_on_ga_operations.py | 298 +++++++++ ..._features_header_on_ga_operations_async.py | 310 ++++++++++ .../test_foundry_features_header_optional.py | 149 ----- ..._foundry_features_header_optional_async.py | 157 ----- 27 files changed, 1166 insertions(+), 1583 deletions(-) delete mode 100644 sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.py delete mode 100644 sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.readme.txt rename sdk/ai/azure-ai-projects/tests/foundry_features_header/{test_foundry_features_header.py => test_foundry_features_header_on_beta_operations.py} (55%) rename sdk/ai/azure-ai-projects/tests/foundry_features_header/{test_foundry_features_header_async.py => test_foundry_features_header_on_beta_operations_async.py} (57%) create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations.py create mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations_async.py delete mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py delete mode 100644 sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py diff --git a/sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.py b/sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.py deleted file mode 100644 index 771285f70d59..000000000000 --- a/sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.py +++ /dev/null @@ -1,568 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -#!/usr/bin/env python3 -""" -Script to apply Foundry Features global opt-in changes to azure-ai-projects SDK. - -Run from the azure-sdk-for-python/sdk/ai/azure-ai-projects/ directory: - python agent-scripts/auto_set_foundry_features.py -""" - -import re -import os - -SYNC_OPS = os.path.join("azure", "ai", "projects", "operations", "_operations.py") -ASYNC_OPS = os.path.join("azure", "ai", "projects", "aio", "operations", "_operations.py") -MODELS_INIT = os.path.join("azure", "ai", "projects", "models", "__init__.py") -MODELS_ENUMS = os.path.join("azure", "ai", "projects", "models", "_enums.py") - - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - - -def read_file(path: str) -> str: - with open(path, "r", encoding="utf-8") as f: - return f.read() - - -def write_file(path: str, content: str) -> None: - with open(path, "w", encoding="utf-8") as f: - f.write(content) - - -def parse_ff_type(line: str): - """Parse the foundry_features parameter type from a parameter line. - - Returns a (ff_type, value) tuple or None. - ff_type values: - 'agent_opt_in' - Optional[Union[str, _models.AgentDefinitionOptInKeys]] - 'optional_literal' - Optional[Literal[FoundryFeaturesOptInKeys.SOME_VALUE]] - 'required_literal' - Literal[FoundryFeaturesOptInKeys.SOME_VALUE] (required) - """ - if "Optional[Union[str, _models.AgentDefinitionOptInKeys]]" in line: - return ("agent_opt_in", None) - m = re.search(r"Optional\[Literal\[FoundryFeaturesOptInKeys\.(\w+)\]\]", line) - if m: - return ("optional_literal", m.group(1)) - m = re.search(r"\bLiteral\[FoundryFeaturesOptInKeys\.(\w+)\]", line) - if m: - return ("required_literal", m.group(1)) - return None - - -def make_local_var(ff_type: str, value, indent: str = " ") -> str: - """Create the _foundry_features local variable declaration line (with trailing newline).""" - if ff_type == "agent_opt_in": - return ( - f"{indent}_foundry_features: Optional[str] = " - f"_get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore\n" - ) - elif ff_type == "optional_literal": - return ( - f"{indent}_foundry_features: Optional[Literal[FoundryFeaturesOptInKeys.{value}]] = " - f"FoundryFeaturesOptInKeys.{value} if self._config.allow_preview else None # type: ignore\n" - ) - elif ff_type == "required_literal": - return ( - f"{indent}_foundry_features: Literal[FoundryFeaturesOptInKeys.{value}] = " - f"FoundryFeaturesOptInKeys.{value}\n" - ) - return "" - - -# --------------------------------------------------------------------------- -# Part 1 – Step 4: Add _foundry_features local variable to implementation methods -# --------------------------------------------------------------------------- - - -def add_local_vars_to_methods(content: str) -> str: - """Add _foundry_features local variable to implementation methods - that still have foundry_features in their signatures. - - Handles both @distributed_trace(d)-decorated and undecorated class methods. - This must be called BEFORE remove_ff_from_signatures so that signatures - still contain the type information needed to generate the local variable. - """ - lines = content.split("\n") - result = [] - - # State tracking - current_decorator = None # 'overload' or 'impl' - in_signature = False - paren_depth = 0 - impl_ff_info = None # (ff_type, ff_value) for current impl method - pending_insertion = False - in_docstring = False - - for line in lines: - # ── Detect class-level decorator (exactly 4 spaces) ────────────────── - m_dec = re.match(r"^ @(overload|distributed_trace(?:_async)?)\s*$", line) - if m_dec: - dec = m_dec.group(1) - if dec == "overload": - current_decorator = "overload" - else: - current_decorator = "impl" - impl_ff_info = None - pending_insertion = False - in_signature = False - result.append(line) - continue - - # ── Detect method definition at class level (exactly 4 spaces) ─────── - # Also handles undecorated methods (current_decorator is None but not after @overload). - # We treat any non-@overload method as a potential impl method. - is_method_def = re.match(r"^ (?:async )?def ", line) - if is_method_def and not in_signature: - # If we just saw @overload, skip this method - is_impl = current_decorator != "overload" - in_signature = True - paren_depth = line.count("(") - line.count(")") - - if is_impl: - impl_ff_info = None - pending_insertion = False - # Scan this first line for foundry_features (single-line signature) - if "foundry_features: " in line: - parsed = parse_ff_type(line) - if parsed: - impl_ff_info = parsed - - if paren_depth <= 0: - in_signature = False - if is_impl and impl_ff_info is not None: - pending_insertion = True - current_decorator = None - - result.append(line) - continue - - # ── Inside method signature ─────────────────────────────────────────── - if in_signature: - paren_depth += line.count("(") - line.count(")") - is_impl = current_decorator != "overload" - - if is_impl and "foundry_features: " in line: - parsed = parse_ff_type(line) - if parsed: - impl_ff_info = parsed - - if paren_depth <= 0: - in_signature = False - if is_impl and impl_ff_info is not None: - pending_insertion = True - current_decorator = None - - result.append(line) - continue - - # ── Track docstrings ────────────────────────────────────────────────── - stripped = line.strip() - if not in_docstring: - if stripped.startswith('"""') or stripped.startswith("'''"): - in_docstring = True - content_after = stripped[3:] - if '"""' in content_after or "'''" in content_after: - in_docstring = False - else: - if '"""' in line or "'''" in line: - in_docstring = False - - # ── Insert local var at method body start ───────────────────────────── - if pending_insertion and not in_docstring and not in_signature: - body_start_patterns = [ - r"^ error_map: MutableMapping = \{", - r"^ _headers = kwargs\.pop\(\"headers\", \{\}\) or \{\}", - r"^ _headers = case_insensitive_dict\(", - ] - for pat in body_start_patterns: - if re.match(pat, line): - ff_type, ff_value = impl_ff_info - local_var = make_local_var(ff_type, ff_value) - result.append(local_var.rstrip("\n")) - impl_ff_info = None - pending_insertion = False - break - - result.append(line) - - return "\n".join(result) - - -# --------------------------------------------------------------------------- -# Part 1 – Steps 2+3: Remove foundry_features from non-build_ signatures -# and docstrings -# --------------------------------------------------------------------------- - - -def remove_ff_from_signatures(content: str) -> str: - """Remove foundry_features parameter from non-build_ method signatures. - - Non-build_ parameters are at 8-space indent; build_ parameters are at - 4-space indent, so indentation distinguishes them. - """ - # Sub-step A: Remove *,\n + foundry_features: when ff is the ONLY keyword-only arg - # (i.e. **kwargs follows immediately after foundry_features) - content = re.sub( - r"\n \*,\n foundry_features: [^\n]+,\n(?= \*\*kwargs)", - "\n", - content, - ) - - # Sub-step B: Remove standalone foundry_features: line at 8-space indent - # (also covers cases where *,\n is NOT right above, meaning other kwargs exist) - content = re.sub( - r"\n foundry_features: [^\n]+,\n", - "\n", - content, - ) - - # Sub-step C: Remove inline foundry_features (only kwarg) in class-method signatures. - # Pattern: "self, ..., *, foundry_features: TYPE, **kwargs: Any" on one 8-space line. - content = re.sub( - r"( self[^\n]*), \*, foundry_features: [^,\n]+, (\*\*kwargs: Any)", - r"\1, \2", - content, - ) - - return content - - -def remove_ff_from_docstrings(content: str) -> str: - """Remove :keyword foundry_features: ... :paramtype foundry_features: blocks.""" - # The :keyword line is at 8-space indent; continuation lines at 9 spaces; - # :paramtype line is at 8-space indent. - content = re.sub( - r" :keyword foundry_features: [^\n]+\n(?: [^\n]+\n)*" - r" :paramtype foundry_features: [^\n]+\n", - "", - content, - ) - return content - - -# --------------------------------------------------------------------------- -# Part 1 – Step 4b: Replace foundry_features=foundry_features in build_ calls -# --------------------------------------------------------------------------- - - -def update_build_calls(content: str) -> str: - """Replace foundry_features=foundry_features, with foundry_features=_foundry_features,.""" - return content.replace( - "foundry_features=foundry_features,", - "foundry_features=_foundry_features,", - ) - - -# --------------------------------------------------------------------------- -# Part 1 – Step 5: Update prepare_request paging next_link HttpRequest calls -# --------------------------------------------------------------------------- - - -def update_paging_requests(content: str) -> str: - """In prepare_request(next_link=None) bodies that use _foundry_features, - add Foundry-Features header to the next-link HttpRequest call.""" - - OLD_HTTP_ARGS = '"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params' - NEW_HTTP_ARGS = ( - '"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, ' - 'headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}' - ) - - def replace_if_ff(match: re.Match) -> str: - body = match.group(1) - suffix = match.group(2) - if "foundry_features=_foundry_features," in body: - body = body.replace(OLD_HTTP_ARGS, NEW_HTTP_ARGS) - return body + suffix - - # Match from "def prepare_request(next_link=None):" up to (but not including) - # "def extract_data" (sync) or "async def extract_data" (async). - pattern = re.compile( - r"( def prepare_request\(next_link=None\):\n.*?)" r"(\n (?:async )?def extract_data)", - re.DOTALL, - ) - return pattern.sub(replace_if_ff, content) - - -# --------------------------------------------------------------------------- -# Part 1 – Step 6: Fix any methods that call foundry_features=_foundry_features -# but never had _foundry_features defined (e.g. LRO wrappers) -# --------------------------------------------------------------------------- - - -def fix_missing_local_var(content: str) -> str: - """Post-processing pass: inject _foundry_features local var into any class - method body that uses foundry_features=_foundry_features but doesn't define - the variable. - - This handles LRO wrapper methods (e.g. _begin_update_memories) that pass - foundry_features through to an _initial method but never had foundry_features - in their own signature. - """ - lines = content.split("\n") - result = [] - - i = 0 - while i < len(lines): - line = lines[i] - - # Detect start of a class method at 4-space indent - if re.match(r"^ (?:async )?def ", line): - # Collect all lines of this method body - method_start = i - method_lines = [line] - j = i + 1 - # Collect until next def at same or lower indent level (or end) - while j < len(lines): - next_line = lines[j] - # A new method or class starts at 4-space or 0-space indent - if next_line and re.match(r"^(?: (?:async )?def |class )", next_line): - break - method_lines.append(next_line) - j += 1 - - # Check if this method uses foundry_features=_foundry_features - # but does NOT define _foundry_features - uses_ff = any("foundry_features=_foundry_features" in ml for ml in method_lines) - defines_ff = any(re.match(r"\s+_foundry_features\s*[=:]", ml) for ml in method_lines) - - if uses_ff and not defines_ff: - # Determine the value from the build_ call or _initial call in the body - # Look for the type annotation from any foundry_features=_foundry_features call context - # We infer the value from the build_ function being called - ff_value = None - for ml in method_lines: - m = re.search( - r"build_\w+_request\b|self\._\w+_initial\(", - ml, - ) - if m: - # Try to find ff value from an existing _initial def in surrounding context - pass - - # Fallback: scan upstream already-built result for the _initial method's _foundry_features - # Instead, find what foundry_features value is used in the associated _initial method - # by scanning for the build_ function name - build_func = None - initial_func = None - for ml in method_lines: - bm = re.search(r"build_(\w+)_request\s*\(", ml) - if bm: - build_func = bm.group(1) - im = re.search(r"self\.(_\w+_initial)\s*\(", ml) - if im: - initial_func = im.group(1) - - # Scan result (already processed lines) for the _foundry_features def - # in the associated _initial method - ff_type = "required_literal" - ff_value = None - if initial_func: - # Find _foundry_features assignment in the initial method we already emitted - for prev_line in result: - m = re.search( - r"_foundry_features:\s*(?:Optional\[)?Literal\[_?FoundryFeaturesOptInKeys\.(\w+)\]", - prev_line, - ) - if m: - ff_value = m.group(1) - # Check if it's Optional - if "Optional" in prev_line: - ff_type = "optional_literal" - else: - ff_type = "required_literal" - # Keep looking for the most recent one (closest to this method) - - if ff_value is None and build_func: - # Derive from build function name: e.g. "beta_memory_stores_update_memories" - # → MEMORY_STORES_V1_PREVIEW - # Use a mapping of known patterns - if "memory_stores" in build_func: - ff_value = "MEMORY_STORES_V1_PREVIEW" - ff_type = "required_literal" - - if ff_value is None: - # Last resort: just copy from the last seen _foundry_features in result - for prev_line in reversed(result): - m = re.search( - r"_foundry_features:\s*(?:Optional\[)?Literal\[_?FoundryFeaturesOptInKeys\.(\w+)\]", - prev_line, - ) - if m: - ff_value = m.group(1) - ff_type = "optional_literal" if "Optional" in prev_line else "required_literal" - break - - if ff_value is not None: - # Find the body start in method_lines and inject the local var - body_start_patterns = [ - r"^ error_map: MutableMapping = \{", - r"^ _headers = kwargs\.pop\(\"headers\", \{\}\) or \{\}", - r"^ _headers = case_insensitive_dict\(", - ] - for k, ml in enumerate(method_lines): - for pat in body_start_patterns: - if re.match(pat, ml): - local_var = make_local_var(ff_type, ff_value).rstrip("\n") - method_lines.insert(k, local_var) - break - else: - continue - break - - result.extend(method_lines) - i = j - continue - - result.append(line) - i += 1 - - return "\n".join(result) - - -# --------------------------------------------------------------------------- -# Main Part 1 orchestrator -# --------------------------------------------------------------------------- - - -def transform_ops_content_part1(content: str, is_sync: bool) -> str: - """Apply all Part 1 transformations to an operations file.""" - - # ── Step 1 (sync only): insert global var after the last top-level import - if is_sync and "_get_agent_definition_opt_in_keys" not in content: - old_import = "from ..models._enums import FoundryFeaturesOptInKeys\n" - new_import = ( - "from ..models._enums import FoundryFeaturesOptInKeys\n" - '_get_agent_definition_opt_in_keys: str = ",".join([_AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value])\n' - ) - content = content.replace(old_import, new_import, 1) - - # Split at first class definition so the header (build_ functions) is untouched - class_match = re.search(r"^class ", content, re.MULTILINE) - if class_match: - header = content[: class_match.start()] - classes = content[class_match.start() :] - else: - header = "" - classes = content - - # ── Steps 3+4: Add local vars BEFORE removing from signatures - classes = add_local_vars_to_methods(classes) - - # ── Step 2: Remove foundry_features from signatures - classes = remove_ff_from_signatures(classes) - - # ── Step 3 (docstrings): Remove :keyword/:paramtype foundry_features blocks - classes = remove_ff_from_docstrings(classes) - - # ── Step 4b: Update build_ call arguments - classes = update_build_calls(classes) - - # ── Step 5: Update paging prepare_request HttpRequest calls - classes = update_paging_requests(classes) - - # ── Step 6: Fix any LRO wrapper methods missing _foundry_features - classes = fix_missing_local_var(classes) - - return header + classes - - -# --------------------------------------------------------------------------- -# Part 2 – Rename internal enum classes and fix imports everywhere -# --------------------------------------------------------------------------- - - -def apply_part2( - sync_ops: str, - async_ops: str, - models_init: str, - models_enums: str, -): - """Rename AgentDefinitionOptInKeys → _AgentDefinitionOptInKeys and - FoundryFeaturesOptInKeys → _FoundryFeaturesOptInKeys everywhere.""" - - # ── _enums.py: rename the two classes ──────────────────────────────────── - models_enums = models_enums.replace( - "class AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta):", - "class _AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta):", - ) - models_enums = models_enums.replace( - "class FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta):", - "class _FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta):", - ) - - # ── models/__init__.py: remove from imports and __all__ ────────────────── - models_init = re.sub(r" AgentDefinitionOptInKeys,\n", "", models_init) - models_init = re.sub(r" FoundryFeaturesOptInKeys,\n", "", models_init) - models_init = re.sub(r' "AgentDefinitionOptInKeys",\n', "", models_init) - models_init = re.sub(r' "FoundryFeaturesOptInKeys",\n', "", models_init) - - # ── Sync _operations.py ────────────────────────────────────────────────── - # Update import to bring in both renamed private classes - sync_ops = sync_ops.replace( - "from ..models._enums import FoundryFeaturesOptInKeys", - "from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys", - ) - # Rename FoundryFeaturesOptInKeys → _FoundryFeaturesOptInKeys - # (use negative lookbehind to avoid double-prefixing _FoundryFeaturesOptInKeys) - sync_ops = re.sub(r"(? None: - print("Reading files…") - sync_ops = read_file(SYNC_OPS) - async_ops = read_file(ASYNC_OPS) - models_init = read_file(MODELS_INIT) - models_enums = read_file(MODELS_ENUMS) - - # ── Part 1 ──────────────────────────────────────────────────────────────── - print("Applying Part 1 to sync operations file…") - sync_ops = transform_ops_content_part1(sync_ops, is_sync=True) - - print("Applying Part 1 to async operations file…") - async_ops = transform_ops_content_part1(async_ops, is_sync=False) - - # ── Part 2 ──────────────────────────────────────────────────────────────── - print("Applying Part 2 (rename internal enum classes)…") - sync_ops, async_ops, models_init, models_enums = apply_part2(sync_ops, async_ops, models_init, models_enums) - - # ── Write results ───────────────────────────────────────────────────────── - print("Writing files…") - write_file(SYNC_OPS, sync_ops) - write_file(ASYNC_OPS, async_ops) - write_file(MODELS_INIT, models_init) - write_file(MODELS_ENUMS, models_enums) - - print("Done.") - - -if __name__ == "__main__": - main() diff --git a/sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.readme.txt b/sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.readme.txt deleted file mode 100644 index 0b95914532dd..000000000000 --- a/sdk/ai/azure-ai-projects/agent-scripts/auto_set_foundry_features.readme.txt +++ /dev/null @@ -1,128 +0,0 @@ -These are the GitHub CoPilot instructions used to edit emitted code, in order to set the -required Foundry-Features HTTP request headers. Before running the Agent, -make sure your package sources code (files under \sdk\ai\azure-ai-projects\azure) do not have any -mypy errors. - -The agent created the Python script "auto_set_foundry_features.py" in this folder. Next time -you re-emit from TypeSpec, try running that script from the \sdk\ai\azure-ai-projects folder -and see if again it did the job correctly. - -Model used was Claude Sonnet 4.6 - -Last updated: 2/18/2026 - ------------------------------ - - -Write a Python script that will apply all the changes mentioned below. -Save the Python script in the folder "agent-scripts" and call it "auto_set_foundry_features.py". -The assumption is that this script runs in the folder azure-sdk-for-python\sdk\ai\azure-ai-projects -to apply the desired changes. - -The Python script is only allowed to edit these four files: -\azure-sdk-for-python\sdk\ai\azure-ai-projects\azure\ai\projects\aio\operations\_operations.py -\azure-sdk-for-python\sdk\ai\azure-ai-projects\azure\ai\projects\operations\_operations.py -\azure-sdk-for-python\sdk\ai\azure-ai-projects\azure\ai\projects\models\__init__.py -\azure-sdk-for-python\sdk\ai\azure-ai-projects\azure\ai\projects\models\_enums.py - -Part 1: - -In Part 1, you are only allowed to edit the two _operation.py files. In this part, your goal -is to update all public methods that have "foundry_features" as input argument, and instead -define them locally as appropriate. You will also fix the paging call in "list" operations, -such that "foundry_features" applies to all paging calls. - -Step 1: -At the top of each Python script, you will find some comments, followed by a section of -"from" and "import" statements. After all of those, insert the following line: - -_get_agent_definition_opt_in_keys: str = ",".join([key.value for key in _models.AgentDefinitionOptInKeys]) - -Step 2: -Find all the methods that show "foundry_features" as an input argument. It could be optional or required. -You can search for the string "foundry_features: " to find all those. But I would like you to ignore all -the methods with names that start with "build_". They should not be modified. - -For each method you find, delete the "foundry_feature" line from the method signature. But take note -of how "foundry_features" is defined here, as you will need that in the next step. -Make sure you also take care of the case where "foundry_features" is the only input arguments (other than -**kwargs). - -Also delete the method doc strings lines associated with this (now deleted) input argument. -Do that by deleting the line that starts with ":keyword foundry_features:", up to and including -the line that starts with ":paramtype foundry_features:". So you may need to delete multiple -lines, not just two lines that include the above strings, but also all lines in between those two lines. - -Step 3: -For each one of the methods you found, declare a new local variable _foundry_features at the top of the method, -that will replace the input argument that was removed in the previous step. - -if "foundry_features" in the method signature was a required input argument before you removed it, -it should have had the form: - -foundry_features: Literal[FoundryFeaturesOptInKeys.SOME_VALUE], - -where "SOME_VALUE" is one of the enum values on FoundryFeaturesOptInKeys. - -In this case, define the new local variable as follows: - -_foundry_features: Literal[FoundryFeaturesOptInKeys.SOME_VALUE] = FoundryFeaturesOptInKeys.SOME_VALUE - -where "SOME_VALUE" is the same value you originally saw in the method signature. - -If "foundry_features" in the method signature was an optional input argument before you removed it, -it should have one of two forms. It could have this form: - -foundry_features: Optional[Union[str, _models.AgentDefinitionOptInKeys]] = None, - -in which case the local argument that you should define is the following: -_foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore - -Or it can have the form: - -foundry_features: Optional[Literal[FoundryFeaturesOptInKeys.SOME_VALUE]] = None, - -where "SOME_VALUE" is one of the enum values on FoundryFeaturesOptInKeys. -in which case the local argument that you should define is the following: - -_foundry_features: Optional[Literal[FoundryFeaturesOptInKeys.SOME_VALUE]] = FoundryFeaturesOptInKeys.SOME_VALUE if self._config.allow_preview else None # type: ignore - -Step 4: -For each one of the methods you found, look for the line "foundry_features=foundry_features" -specifying an input argument to a function call, where the function name starts with "build_". -Replace that with "foundry_features=_foundry_features". - -Step 5: -For each one of the methods you found, also see if it includes a function definition with -the following signature: -def prepare_request(next_link=None): - -If so, look for the following line inside that function: -"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params - -And replace it with this line: -"GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")} - -Part 2: - -In Part 2, you are allowed to edit all four files mentioned above. No other files. In this part you will: -* Rename the class AgentDefinitionOptInKeys to _AgentDefinitionOptInKeys and make it internal. -* Rename the class FoundryFeaturesOptInKeys to _FoundryFeaturesOptInKeys and make it internal. -Which means you need to remove them from the relevant __init__.py file and fix all imports as a result. - -Verification: - -After your created the Python script and saved it to disk, run it in the folder \azure-sdk-for-python\sdk\ai\azure-ai-projects -in order to apply the changes mentioned above. - -To verify your result, first run the 'black' tool, which validates the formatting of the code: -black --config ../../../eng/black-pyproject.toml . - -Once that is error free, run MyPy tool in the following way: -tox run -e mypy -c ../../../eng/tox/tox.ini --root . - -and make sure there are no errors in the files under folder \azure-sdk-for-python\sdk\ai\azure-ai-projects\azure . -Note there may be errors in sample or test code. Ignore those. - -If there are any errors during verification, update the auto_set_foundry_features.py script to make sure those -will not appear again. diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json index 67119b7f250f..cc052dec8166 100644 --- a/sdk/ai/azure-ai-projects/apiview-properties.json +++ b/sdk/ai/azure-ai-projects/apiview-properties.json @@ -219,7 +219,6 @@ "azure.ai.projects.models.WorkIQPreviewToolParameters": "Azure.AI.Projects.WorkIQPreviewToolParameters", "azure.ai.projects.models.EvaluationTaxonomyInputType": "Azure.AI.Projects.EvaluationTaxonomyInputType", "azure.ai.projects.models.RiskCategory": "Azure.AI.Projects.RiskCategory", - "azure.ai.projects.models.FoundryFeaturesOptInKeys": "Azure.AI.Projects.FoundryFeaturesOptInKeys", "azure.ai.projects.models.EvaluatorType": "Azure.AI.Projects.EvaluatorType", "azure.ai.projects.models.EvaluatorCategory": "Azure.AI.Projects.EvaluatorCategory", "azure.ai.projects.models.EvaluatorDefinitionType": "Azure.AI.Projects.EvaluatorDefinitionType", @@ -261,7 +260,6 @@ "azure.ai.projects.models.AgentProtocol": "Azure.AI.Projects.AgentProtocol", "azure.ai.projects.models.ToolChoiceParamType": "OpenAI.ToolChoiceParamType", "azure.ai.projects.models.TextResponseFormatConfigurationType": "OpenAI.TextResponseFormatConfigurationType", - "azure.ai.projects.models.AgentDefinitionOptInKeys": "Azure.AI.Projects.AgentDefinitionOptInKeys", "azure.ai.projects.models.EvaluationRuleActionType": "Azure.AI.Projects.EvaluationRuleActionType", "azure.ai.projects.models.EvaluationRuleEventType": "Azure.AI.Projects.EvaluationRuleEventType", "azure.ai.projects.models.ConnectionType": "Azure.AI.Projects.ConnectionType", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 3e78f121e181..50659948669c 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -35,9 +35,7 @@ from ... import models as _models from ..._utils.model_base import SdkJSONEncoder, _deserialize, _failsafe_deserialize from ..._utils.serialization import Deserializer, Serializer -from ...models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys from ...operations._operations import ( - _get_agent_definition_opt_in_keys, build_agents_create_version_from_manifest_request, build_agents_create_version_request, build_agents_delete_request, @@ -113,8 +111,6 @@ T = TypeVar("T") ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]] List = list -_SERIALIZER = Serializer() -_SERIALIZER.client_side_validation = False class BetaOperations: # pylint: disable=too-many-instance-attributes @@ -511,8 +507,6 @@ async def create_version( :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore - error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -541,7 +535,6 @@ async def create_version( _request = build_agents_create_version_request( agent_name=agent_name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -1220,7 +1213,6 @@ async def create_or_update( :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -1244,7 +1236,6 @@ async def create_or_update( _request = build_evaluation_rules_create_or_update_request( id=id, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3000,9 +2991,6 @@ async def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3018,7 +3006,6 @@ async def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: _request = build_beta_evaluation_taxonomies_get_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3069,9 +3056,6 @@ def list( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluationTaxonomy] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -3089,7 +3073,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_evaluation_taxonomies_list_request( - foundry_features=_foundry_features, input_name=input_name, input_type=input_type, api_version=self._config.api_version, @@ -3117,7 +3100,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3165,9 +3148,6 @@ async def delete(self, name: str, **kwargs: Any) -> None: :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3183,7 +3163,6 @@ async def delete(self, name: str, **kwargs: Any) -> None: _request = build_beta_evaluation_taxonomies_delete_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3276,9 +3255,6 @@ async def create( :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3302,7 +3278,6 @@ async def create( _request = build_beta_evaluation_taxonomies_create_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3410,9 +3385,6 @@ async def update( :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3436,7 +3408,6 @@ async def update( _request = build_beta_evaluation_taxonomies_update_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3517,9 +3488,6 @@ def list_versions( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -3538,7 +3506,6 @@ def prepare_request(next_link=None): _request = build_beta_evaluators_list_versions_request( name=name, - foundry_features=_foundry_features, type=type, limit=limit, api_version=self._config.api_version, @@ -3566,7 +3533,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3625,9 +3592,6 @@ def list( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -3645,7 +3609,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_evaluators_list_request( - foundry_features=_foundry_features, type=type, limit=limit, api_version=self._config.api_version, @@ -3673,7 +3636,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3724,9 +3687,6 @@ async def get_version(self, name: str, version: str, **kwargs: Any) -> _models.E :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3743,7 +3703,6 @@ async def get_version(self, name: str, version: str, **kwargs: Any) -> _models.E _request = build_beta_evaluators_get_version_request( name=name, version=version, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3793,9 +3752,6 @@ async def delete_version(self, name: str, version: str, **kwargs: Any) -> None: :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3812,7 +3768,6 @@ async def delete_version(self, name: str, version: str, **kwargs: Any) -> None: _request = build_beta_evaluators_delete_version_request( name=name, version=version, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -3910,9 +3865,6 @@ async def create_version( :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3936,7 +3888,6 @@ async def create_version( _request = build_beta_evaluators_create_version_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -4068,9 +4019,6 @@ async def update_version( :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4095,7 +4043,6 @@ async def update_version( _request = build_beta_evaluators_update_version_request( name=name, version=version, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -4234,9 +4181,6 @@ async def pending_upload( :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4261,7 +4205,6 @@ async def pending_upload( _request = build_beta_evaluators_pending_upload_request( name=name, version=version, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -4400,9 +4343,6 @@ async def get_credentials( :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4427,7 +4367,6 @@ async def get_credentials( _request = build_beta_evaluators_get_credentials_request( name=name, version=version, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -4546,9 +4485,6 @@ async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwa :rtype: ~azure.ai.projects.models.Insight :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4571,7 +4507,6 @@ async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwa _content = json.dumps(insight, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_insights_generate_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -4598,7 +4533,11 @@ async def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwa except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() @@ -4625,9 +4564,6 @@ async def get( :rtype: ~azure.ai.projects.models.Insight :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4643,7 +4579,6 @@ async def get( _request = build_beta_insights_get_request( insight_id=insight_id, - foundry_features=_foundry_features, include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, @@ -4669,7 +4604,11 @@ async def get( except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() @@ -4710,9 +4649,6 @@ def list( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Insight] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -4730,7 +4666,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_insights_list_request( - foundry_features=_foundry_features, type=type, eval_id=eval_id, run_id=run_id, @@ -4761,7 +4696,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -4793,7 +4728,11 @@ async def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) return pipeline_response @@ -4907,9 +4846,6 @@ async def create( :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4939,7 +4875,6 @@ async def create( _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_memory_stores_create_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5070,9 +5005,6 @@ async def update( :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5099,7 +5031,6 @@ async def update( _request = build_beta_memory_stores_update_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5152,9 +5083,6 @@ async def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5170,7 +5098,6 @@ async def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: _request = build_beta_memory_stores_get_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5240,9 +5167,6 @@ def list( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.MemoryStoreDetails] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -5259,7 +5183,6 @@ def list( def prepare_request(_continuation_token=None): _request = build_beta_memory_stores_list_request( - foundry_features=_foundry_features, limit=limit, order=order, after=_continuation_token, @@ -5315,9 +5238,6 @@ async def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreRes :rtype: ~azure.ai.projects.models.DeleteMemoryStoreResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5333,7 +5253,6 @@ async def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreRes _request = build_beta_memory_stores_delete_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5427,9 +5346,6 @@ async def _search_memories( :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5463,7 +5379,6 @@ async def _search_memories( _request = build_beta_memory_stores_search_memories_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5517,9 +5432,6 @@ async def _update_memories_initial( update_delay: Optional[int] = None, **kwargs: Any ) -> AsyncIterator[bytes]: - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5553,7 +5465,6 @@ async def _update_memories_initial( _request = build_beta_memory_stores_update_memories_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5654,9 +5565,6 @@ async def _begin_update_memories( ~azure.core.polling.AsyncLROPoller[~azure.ai.projects.models.MemoryStoreUpdateCompletedResult] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} @@ -5669,7 +5577,6 @@ async def _begin_update_memories( raw_result = await self._update_memories_initial( name=name, body=body, - foundry_features=_foundry_features, scope=scope, items=items, previous_update_id=previous_update_id, @@ -5795,9 +5702,6 @@ async def delete_scope( :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5826,7 +5730,6 @@ async def delete_scope( _request = build_beta_memory_stores_delete_scope_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5897,9 +5800,6 @@ async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5915,7 +5815,6 @@ async def get(self, name: str, **kwargs: Any) -> _models.RedTeam: _request = build_beta_red_teams_get_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5960,9 +5859,6 @@ def list(self, **kwargs: Any) -> AsyncItemPaged["_models.RedTeam"]: :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.RedTeam] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -5980,7 +5876,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_red_teams_list_request( - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6006,7 +5901,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6101,9 +5996,6 @@ async def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwar :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6126,7 +6018,6 @@ async def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwar _content = json.dumps(red_team, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_red_teams_create_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6197,9 +6088,6 @@ async def delete(self, schedule_id: str, **kwargs: Any) -> None: :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6215,7 +6103,6 @@ async def delete(self, schedule_id: str, **kwargs: Any) -> None: _request = build_beta_schedules_delete_request( schedule_id=schedule_id, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6249,9 +6136,6 @@ async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6267,7 +6151,6 @@ async def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: _request = build_beta_schedules_get_request( schedule_id=schedule_id, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6323,9 +6206,6 @@ def list( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.Schedule] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -6343,7 +6223,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_schedules_list_request( - foundry_features=_foundry_features, type=type, enabled=enabled, api_version=self._config.api_version, @@ -6371,7 +6250,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6478,9 +6357,6 @@ async def create_or_update( :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6504,7 +6380,6 @@ async def create_or_update( _request = build_beta_schedules_create_or_update_request( schedule_id=schedule_id, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6555,9 +6430,6 @@ async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models :rtype: ~azure.ai.projects.models.ScheduleRun :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6574,7 +6446,6 @@ async def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models _request = build_beta_schedules_get_run_request( schedule_id=schedule_id, run_id=run_id, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -6637,9 +6508,6 @@ def list_runs( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.ScheduleRun] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -6658,7 +6526,6 @@ def prepare_request(next_link=None): _request = build_beta_schedules_list_runs_request( schedule_id=schedule_id, - foundry_features=_foundry_features, type=type, enabled=enabled, api_version=self._config.api_version, @@ -6686,7 +6553,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6832,9 +6699,6 @@ async def create( :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6864,7 +6728,6 @@ async def create( _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_toolsets_create_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -7001,9 +6864,6 @@ async def update( :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7032,7 +6892,6 @@ async def update( _request = build_beta_toolsets_update_request( tool_set_name=tool_set_name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -7085,9 +6944,6 @@ async def get(self, tool_set_name: str, **kwargs: Any) -> _models.ToolsetObject: :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7103,7 +6959,6 @@ async def get(self, tool_set_name: str, **kwargs: Any) -> _models.ToolsetObject: _request = build_beta_toolsets_get_request( tool_set_name=tool_set_name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -7173,9 +7028,6 @@ def list( :rtype: ~azure.core.async_paging.AsyncItemPaged[~azure.ai.projects.models.ToolsetObject] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -7192,7 +7044,6 @@ def list( def prepare_request(_continuation_token=None): _request = build_beta_toolsets_list_request( - foundry_features=_foundry_features, limit=limit, order=order, after=_continuation_token, @@ -7248,9 +7099,6 @@ async def delete(self, tool_set_name: str, **kwargs: Any) -> _models.DeleteTools :rtype: ~azure.ai.projects.models.DeleteToolsetResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7266,7 +7114,6 @@ async def delete(self, tool_set_name: str, **kwargs: Any) -> _models.DeleteTools _request = build_beta_toolsets_delete_request( tool_set_name=tool_set_name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index 2515113bbf86..c448e46abae8 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -12,10 +12,11 @@ from ._patch_agents_async import AgentsOperations from ._patch_datasets_async import DatasetsOperations from ._patch_evaluation_rules_async import EvaluationRulesOperations -from ._patch_evaluators_async import EvaluatorsOperations as BetaEvaluatorsOperations +from ._patch_evaluators_async import BetaEvaluatorsOperations from ._patch_telemetry_async import TelemetryOperations from ._patch_connections_async import ConnectionsOperations from ._patch_memories_async import BetaMemoryStoresOperations +from ...operations._patch import _BETA_OPERATION_FEATURE_HEADERS, _OperationMethodHeaderProxy from ._operations import ( BetaEvaluationTaxonomiesOperations, BetaInsightsOperations, @@ -58,6 +59,13 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Replace with patched class that includes begin_update_memories self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) + for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): + setattr( + self, + property_name, + _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), + ) + __all__: List[str] = [ "AgentsOperations", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py index 0ed177f12ab9..f2a6dbc62624 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py @@ -10,9 +10,15 @@ from typing import Union, Optional, Any, IO, overload from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator_async import distributed_trace_async from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset from ... import models as _models -from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE +from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive +from ...operations._patch_agents import ( + _AGENT_OPERATION_FEATURE_HEADERS, + _PREVIEW_FEATURE_REQUIRED_CODE, + _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE, +) class AgentsOperations(GeneratedAgentsOperations): @@ -114,6 +120,7 @@ async def create_version( """ ... + @distributed_trace_async async def create_version( self, agent_name: str, @@ -151,6 +158,15 @@ async def create_version( :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS + kwargs["headers"] = headers + try: return await super().create_version( agent_name, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py index 08ab156a9bbe..a296493c469b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py @@ -10,9 +10,12 @@ from typing import Union, Any, IO, overload from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator_async import distributed_trace_async from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON from ... import models as _models from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE +from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): @@ -82,6 +85,7 @@ async def create_or_update( """ ... + @distributed_trace_async async def create_or_update( self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any ) -> _models.EvaluationRule: @@ -96,6 +100,17 @@ async def create_or_update( :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = { + _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + } + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + kwargs["headers"] = headers + try: return await super().create_or_update(id, evaluation_rule, **kwargs) except HttpResponseError as exc: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py index f0ee18c53178..1f043ffc8bef 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -16,7 +16,9 @@ from azure.storage.blob.aio import ContainerClient from azure.core.tracing.decorator_async import distributed_trace_async from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from ._operations import BetaEvaluatorsOperations as EvaluatorsOperationsGenerated, JSON +from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON +from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME from ...models._models import ( CodeBasedEvaluatorDefinition, EvaluatorVersion, @@ -24,8 +26,10 @@ logger = logging.getLogger(__name__) +_EVALUATORS_FOUNDRY_FEATURES_VALUE = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value -class EvaluatorsOperations(EvaluatorsOperationsGenerated): + +class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): """ .. warning:: **DO NOT** instantiate this class directly. @@ -139,6 +143,7 @@ async def _start_pending_upload_and_get_container_client( name=name, version=version, pending_upload_request=request_body, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, ) # The service returns blobReferenceForConsumption @@ -174,7 +179,9 @@ async def _get_next_version(self, name: str) -> str: """ try: versions = [] - async for v in self.list_versions(name=name): + async for v in self.list_versions( + name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} + ): versions.append(v) if versions: numeric_versions = [] @@ -245,6 +252,7 @@ async def upload( result = await self.create_version( name=name, evaluator_version=evaluator_version, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, ) return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py index 86fa98998195..856d3433a6a7 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py @@ -21,8 +21,11 @@ MemoryStoreUpdateCompletedResult, AsyncUpdateMemoriesLROPoller, ) -from ...models._patch import _AsyncUpdateMemoriesLROPollingMethod -from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._patch import ( + _AsyncUpdateMemoriesLROPollingMethod, + _FOUNDRY_FEATURES_HEADER_NAME, + _BETA_OPERATION_FEATURE_HEADERS, +) from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations from ...operations._patch_memories import _serialize_memory_input_items from ..._validation import api_version_validation @@ -354,7 +357,7 @@ def get_long_running_output(pipeline_response): polling_method: _AsyncUpdateMemoriesLROPollingMethod = _AsyncUpdateMemoriesLROPollingMethod( lro_delay, path_format_arguments=path_format_arguments, - headers={"Foundry-Features": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value}, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, **kwargs, ) else: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index 2974e60e06a3..f009264826d7 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -2792,7 +2792,7 @@ class ContainerNetworkPolicyAllowlistParam(ContainerNetworkPolicyParam, discrimi allowed_domains: list[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of allowed domains when type is ``allowlist``. Required.""" domain_secrets: Optional[list["_models.ContainerNetworkPolicyDomainSecretParam"]] = rest_field( - visibility=["create"] + visibility=["read", "create", "update", "delete", "query"] ) """Optional domain-scoped secrets for allowlisted domains.""" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index ed7f7e804883..474b8b96fb86 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -18,6 +18,38 @@ from azure.core.polling.async_base_polling import AsyncLROBasePolling from ._models import CustomCredential as CustomCredentialGenerated from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult +from ._enums import _FoundryFeaturesOptInKeys + +_FOUNDRY_FEATURES_HEADER_NAME: str = "Foundry-Features" +"""The HTTP header name used to opt in to Foundry preview features.""" + +_BETA_OPERATION_FEATURE_HEADERS: dict = { + "evaluation_taxonomies": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "evaluators": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "insights": _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, + "memory_stores": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, + "red_teams": _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, + "schedules": _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, + "toolsets": _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, +} +"""Foundry-Features header values keyed by beta sub-client property name.""" + + +def _has_header_case_insensitive(headers: Any, header_name: str) -> bool: + """Return True if headers already contains the provided header name. + + :param headers: The headers mapping to search. + :type headers: Any + :param header_name: The header name to look for (case-insensitive). + :type header_name: str + :return: True if the header is present, False otherwise. + :rtype: bool + """ + try: + header_name_lower = header_name.lower() + return any(str(key).lower() == header_name_lower for key in headers) + except Exception: # pylint: disable=broad-except + return False class CustomCredential(CustomCredentialGenerated, discriminator="CustomKeys"): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index 55f80af3bb5c..f320a58a57b0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -37,15 +37,6 @@ from .._configuration import AIProjectClientConfiguration from .._utils.model_base import SdkJSONEncoder, _deserialize, _failsafe_deserialize from .._utils.serialization import Deserializer, Serializer -from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys - -_get_agent_definition_opt_in_keys: str = ",".join( - [ - _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, - _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, - _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, - ] -) JSON = MutableMapping[str, Any] _Unset: Any = object() @@ -142,14 +133,7 @@ def build_agents_list_request( return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_agents_create_version_request( - agent_name: str, - *, - foundry_features: Optional[ - Union[str, _AgentDefinitionOptInKeys, Literal[_FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW]] - ] = None, - **kwargs: Any -) -> HttpRequest: +def build_agents_create_version_request(agent_name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -169,8 +153,6 @@ def build_agents_create_version_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - if foundry_features is not None: - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -340,10 +322,7 @@ def build_evaluation_rules_delete_request(id: str, **kwargs: Any) -> HttpRequest def build_evaluation_rules_create_or_update_request( # pylint: disable=name-too-long - id: str, - *, - foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = None, - **kwargs: Any + id: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -364,8 +343,6 @@ def build_evaluation_rules_create_or_update_request( # pylint: disable=name-too _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - if foundry_features is not None: - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -821,7 +798,7 @@ def build_indexes_create_or_update_request(name: str, version: str, **kwargs: An def build_beta_evaluation_taxonomies_get_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -841,18 +818,13 @@ def build_beta_evaluation_taxonomies_get_request( # pylint: disable=name-too-lo _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_evaluation_taxonomies_list_request( # pylint: disable=name-too-long - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], - input_name: Optional[str] = None, - input_type: Optional[str] = None, - **kwargs: Any + *, input_name: Optional[str] = None, input_type: Optional[str] = None, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -871,16 +843,14 @@ def build_beta_evaluation_taxonomies_list_request( # pylint: disable=name-too-l _params["inputType"] = _SERIALIZER.query("input_type", input_type, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_evaluation_taxonomies_delete_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) @@ -895,14 +865,11 @@ def build_beta_evaluation_taxonomies_delete_request( # pylint: disable=name-too # Construct parameters _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") - - return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) + return HttpRequest(method="DELETE", url=_url, params=_params, **kwargs) def build_beta_evaluation_taxonomies_create_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -923,7 +890,6 @@ def build_beta_evaluation_taxonomies_create_request( # pylint: disable=name-too _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -932,7 +898,7 @@ def build_beta_evaluation_taxonomies_create_request( # pylint: disable=name-too def build_beta_evaluation_taxonomies_update_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -953,7 +919,6 @@ def build_beta_evaluation_taxonomies_update_request( # pylint: disable=name-too _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -964,7 +929,6 @@ def build_beta_evaluation_taxonomies_update_request( # pylint: disable=name-too def build_beta_evaluators_list_versions_request( # pylint: disable=name-too-long name: str, *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, limit: Optional[int] = None, **kwargs: Any @@ -991,7 +955,6 @@ def build_beta_evaluators_list_versions_request( # pylint: disable=name-too-lon _params["limit"] = _SERIALIZER.query("limit", limit, "int") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) @@ -999,7 +962,6 @@ def build_beta_evaluators_list_versions_request( # pylint: disable=name-too-lon def build_beta_evaluators_list_request( *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], type: Optional[Union[Literal["builtin"], Literal["custom"], Literal["all"], str]] = None, limit: Optional[int] = None, **kwargs: Any @@ -1021,18 +983,13 @@ def build_beta_evaluators_list_request( _params["limit"] = _SERIALIZER.query("limit", limit, "int") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_evaluators_get_version_request( # pylint: disable=name-too-long - name: str, - version: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], - **kwargs: Any + name: str, version: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1053,20 +1010,14 @@ def build_beta_evaluators_get_version_request( # pylint: disable=name-too-long _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_evaluators_delete_version_request( # pylint: disable=name-too-long - name: str, - version: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], - **kwargs: Any + name: str, version: str, **kwargs: Any ) -> HttpRequest: - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) @@ -1082,14 +1033,11 @@ def build_beta_evaluators_delete_version_request( # pylint: disable=name-too-lo # Construct parameters _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") - - return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) + return HttpRequest(method="DELETE", url=_url, params=_params, **kwargs) def build_beta_evaluators_create_version_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1110,7 +1058,6 @@ def build_beta_evaluators_create_version_request( # pylint: disable=name-too-lo _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1119,11 +1066,7 @@ def build_beta_evaluators_create_version_request( # pylint: disable=name-too-lo def build_beta_evaluators_update_version_request( # pylint: disable=name-too-long - name: str, - version: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], - **kwargs: Any + name: str, version: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1145,7 +1088,6 @@ def build_beta_evaluators_update_version_request( # pylint: disable=name-too-lo _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1154,11 +1096,7 @@ def build_beta_evaluators_update_version_request( # pylint: disable=name-too-lo def build_beta_evaluators_pending_upload_request( # pylint: disable=name-too-long - name: str, - version: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], - **kwargs: Any + name: str, version: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1180,7 +1118,6 @@ def build_beta_evaluators_pending_upload_request( # pylint: disable=name-too-lo _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1189,11 +1126,7 @@ def build_beta_evaluators_pending_upload_request( # pylint: disable=name-too-lo def build_beta_evaluators_get_credentials_request( # pylint: disable=name-too-long - name: str, - version: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW], - **kwargs: Any + name: str, version: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1215,7 +1148,6 @@ def build_beta_evaluators_get_credentials_request( # pylint: disable=name-too-l _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1223,9 +1155,7 @@ def build_beta_evaluators_get_credentials_request( # pylint: disable=name-too-l return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_insights_generate_request( - *, foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_insights_generate_request(**kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1240,7 +1170,6 @@ def build_beta_insights_generate_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if "Repeatability-Request-ID" not in _headers: _headers["Repeatability-Request-ID"] = str(uuid.uuid4()) if "Repeatability-First-Sent" not in _headers: @@ -1255,11 +1184,7 @@ def build_beta_insights_generate_request( def build_beta_insights_get_request( - insight_id: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW], - include_coordinates: Optional[bool] = None, - **kwargs: Any + insight_id: str, *, include_coordinates: Optional[bool] = None, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1276,12 +1201,11 @@ def build_beta_insights_get_request( _url: str = _url.format(**path_format_arguments) # type: ignore # Construct parameters - _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") if include_coordinates is not None: _params["includeCoordinates"] = _SERIALIZER.query("include_coordinates", include_coordinates, "bool") + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) @@ -1289,7 +1213,6 @@ def build_beta_insights_get_request( def build_beta_insights_list_request( *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW], type: Optional[Union[str, _models.InsightType]] = None, eval_id: Optional[str] = None, run_id: Optional[str] = None, @@ -1307,7 +1230,6 @@ def build_beta_insights_list_request( _url = "/insights" # Construct parameters - _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") if type is not None: _params["type"] = _SERIALIZER.query("type", type, "str") if eval_id is not None: @@ -1318,17 +1240,15 @@ def build_beta_insights_list_request( _params["agentName"] = _SERIALIZER.query("agent_name", agent_name, "str") if include_coordinates is not None: _params["includeCoordinates"] = _SERIALIZER.query("include_coordinates", include_coordinates, "bool") + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_memory_stores_create_request( - *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_memory_stores_create_request(**kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1343,7 +1263,6 @@ def build_beta_memory_stores_create_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1351,9 +1270,7 @@ def build_beta_memory_stores_create_request( return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_memory_stores_update_request( - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_memory_stores_update_request(name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1373,7 +1290,6 @@ def build_beta_memory_stores_update_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1381,9 +1297,7 @@ def build_beta_memory_stores_update_request( return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_memory_stores_get_request( - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_memory_stores_get_request(name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1402,7 +1316,6 @@ def build_beta_memory_stores_get_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) @@ -1410,7 +1323,6 @@ def build_beta_memory_stores_get_request( def build_beta_memory_stores_list_request( *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], limit: Optional[int] = None, order: Optional[Union[str, _models.PageOrder]] = None, after: Optional[str] = None, @@ -1438,15 +1350,12 @@ def build_beta_memory_stores_list_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_memory_stores_delete_request( - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_memory_stores_delete_request(name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1465,14 +1374,13 @@ def build_beta_memory_stores_delete_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_memory_stores_search_memories_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1493,7 +1401,6 @@ def build_beta_memory_stores_search_memories_request( # pylint: disable=name-to _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1502,7 +1409,7 @@ def build_beta_memory_stores_search_memories_request( # pylint: disable=name-to def build_beta_memory_stores_update_memories_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1523,7 +1430,6 @@ def build_beta_memory_stores_update_memories_request( # pylint: disable=name-to _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1532,7 +1438,7 @@ def build_beta_memory_stores_update_memories_request( # pylint: disable=name-to def build_beta_memory_stores_delete_scope_request( # pylint: disable=name-too-long - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW], **kwargs: Any + name: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1553,7 +1459,6 @@ def build_beta_memory_stores_delete_scope_request( # pylint: disable=name-too-l _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1561,9 +1466,7 @@ def build_beta_memory_stores_delete_scope_request( # pylint: disable=name-too-l return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_red_teams_get_request( - name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_red_teams_get_request(name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1582,15 +1485,12 @@ def build_beta_red_teams_get_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_red_teams_list_request( - *, foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_red_teams_list_request(**kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1604,15 +1504,12 @@ def build_beta_red_teams_list_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_red_teams_create_request( - *, foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_red_teams_create_request(**kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1627,7 +1524,6 @@ def build_beta_red_teams_create_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1635,10 +1531,7 @@ def build_beta_red_teams_create_request( return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_schedules_delete_request( - schedule_id: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) +def build_beta_schedules_delete_request(schedule_id: str, **kwargs: Any) -> HttpRequest: _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) api_version: str = kwargs.pop("api_version", _params.pop("api-version", "v1")) @@ -1653,15 +1546,10 @@ def build_beta_schedules_delete_request( # Construct parameters _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") - # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") - - return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) + return HttpRequest(method="DELETE", url=_url, params=_params, **kwargs) -def build_beta_schedules_get_request( - schedule_id: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_schedules_get_request(schedule_id: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1680,18 +1568,13 @@ def build_beta_schedules_get_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_schedules_list_request( - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW], - type: Optional[Union[str, _models.ScheduleTaskType]] = None, - enabled: Optional[bool] = None, - **kwargs: Any + *, type: Optional[Union[str, _models.ScheduleTaskType]] = None, enabled: Optional[bool] = None, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1710,14 +1593,13 @@ def build_beta_schedules_list_request( _params["enabled"] = _SERIALIZER.query("enabled", enabled, "bool") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) def build_beta_schedules_create_or_update_request( # pylint: disable=name-too-long - schedule_id: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW], **kwargs: Any + schedule_id: str, **kwargs: Any ) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1738,7 +1620,6 @@ def build_beta_schedules_create_or_update_request( # pylint: disable=name-too-l _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1746,13 +1627,7 @@ def build_beta_schedules_create_or_update_request( # pylint: disable=name-too-l return HttpRequest(method="PUT", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_schedules_get_run_request( - schedule_id: str, - run_id: str, - *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW], - **kwargs: Any -) -> HttpRequest: +def build_beta_schedules_get_run_request(schedule_id: str, run_id: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1772,7 +1647,6 @@ def build_beta_schedules_get_run_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) @@ -1781,7 +1655,6 @@ def build_beta_schedules_get_run_request( def build_beta_schedules_list_runs_request( schedule_id: str, *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW], type: Optional[Union[str, _models.ScheduleTaskType]] = None, enabled: Optional[bool] = None, **kwargs: Any @@ -1808,15 +1681,12 @@ def build_beta_schedules_list_runs_request( _params["enabled"] = _SERIALIZER.query("enabled", enabled, "bool") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_toolsets_create_request( - *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_toolsets_create_request(**kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1831,7 +1701,6 @@ def build_beta_toolsets_create_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1839,9 +1708,7 @@ def build_beta_toolsets_create_request( return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_toolsets_update_request( - tool_set_name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_toolsets_update_request(tool_set_name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1861,7 +1728,6 @@ def build_beta_toolsets_update_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") if content_type is not None: _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") @@ -1869,9 +1735,7 @@ def build_beta_toolsets_update_request( return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_toolsets_get_request( - tool_set_name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_toolsets_get_request(tool_set_name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1890,7 +1754,6 @@ def build_beta_toolsets_get_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) @@ -1898,7 +1761,6 @@ def build_beta_toolsets_get_request( def build_beta_toolsets_list_request( *, - foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], limit: Optional[int] = None, order: Optional[Union[str, _models.PageOrder]] = None, after: Optional[str] = None, @@ -1926,15 +1788,12 @@ def build_beta_toolsets_list_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) -def build_beta_toolsets_delete_request( - tool_set_name: str, *, foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW], **kwargs: Any -) -> HttpRequest: +def build_beta_toolsets_delete_request(tool_set_name: str, **kwargs: Any) -> HttpRequest: _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) @@ -1953,7 +1812,6 @@ def build_beta_toolsets_delete_request( _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") # Construct headers - _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") return HttpRequest(method="DELETE", url=_url, params=_params, headers=_headers, **kwargs) @@ -2353,8 +2211,6 @@ def create_version( :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[str] = _get_agent_definition_opt_in_keys if self._config.allow_preview else None # type: ignore - error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -2383,7 +2239,6 @@ def create_version( _request = build_agents_create_version_request( agent_name=agent_name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -3060,7 +2915,6 @@ def create_or_update( :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Optional[Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW]] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW if self._config.allow_preview else None # type: ignore error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -3084,7 +2938,6 @@ def create_or_update( _request = build_evaluation_rules_create_or_update_request( id=id, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -4840,9 +4693,6 @@ def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -4858,7 +4708,6 @@ def get(self, name: str, **kwargs: Any) -> _models.EvaluationTaxonomy: _request = build_beta_evaluation_taxonomies_get_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -4909,9 +4758,6 @@ def list( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluationTaxonomy] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -4929,7 +4775,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_evaluation_taxonomies_list_request( - foundry_features=_foundry_features, input_name=input_name, input_type=input_type, api_version=self._config.api_version, @@ -4957,7 +4802,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5005,9 +4850,6 @@ def delete(self, name: str, **kwargs: Any) -> None: # pylint: disable=inconsist :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5023,7 +4865,6 @@ def delete(self, name: str, **kwargs: Any) -> None: # pylint: disable=inconsist _request = build_beta_evaluation_taxonomies_delete_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5116,9 +4957,6 @@ def create( :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5142,7 +4980,6 @@ def create( _request = build_beta_evaluation_taxonomies_create_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5250,9 +5087,6 @@ def update( :rtype: ~azure.ai.projects.models.EvaluationTaxonomy :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5276,7 +5110,6 @@ def update( _request = build_beta_evaluation_taxonomies_update_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5357,9 +5190,6 @@ def list_versions( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -5378,7 +5208,6 @@ def prepare_request(next_link=None): _request = build_beta_evaluators_list_versions_request( name=name, - foundry_features=_foundry_features, type=type, limit=limit, api_version=self._config.api_version, @@ -5406,7 +5235,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5465,9 +5294,6 @@ def list( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.EvaluatorVersion] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -5485,7 +5311,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_evaluators_list_request( - foundry_features=_foundry_features, type=type, limit=limit, api_version=self._config.api_version, @@ -5513,7 +5338,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5564,9 +5389,6 @@ def get_version(self, name: str, version: str, **kwargs: Any) -> _models.Evaluat :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5583,7 +5405,6 @@ def get_version(self, name: str, version: str, **kwargs: Any) -> _models.Evaluat _request = build_beta_evaluators_get_version_request( name=name, version=version, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5635,9 +5456,6 @@ def delete_version( # pylint: disable=inconsistent-return-statements :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5654,7 +5472,6 @@ def delete_version( # pylint: disable=inconsistent-return-statements _request = build_beta_evaluators_delete_version_request( name=name, version=version, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -5752,9 +5569,6 @@ def create_version( :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5778,7 +5592,6 @@ def create_version( _request = build_beta_evaluators_create_version_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -5910,9 +5723,6 @@ def update_version( :rtype: ~azure.ai.projects.models.EvaluatorVersion :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -5937,7 +5747,6 @@ def update_version( _request = build_beta_evaluators_update_version_request( name=name, version=version, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6076,9 +5885,6 @@ def pending_upload( :rtype: ~azure.ai.projects.models.PendingUploadResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6103,7 +5909,6 @@ def pending_upload( _request = build_beta_evaluators_pending_upload_request( name=name, version=version, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6242,9 +6047,6 @@ def get_credentials( :rtype: ~azure.ai.projects.models.DatasetCredential :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6269,7 +6071,6 @@ def get_credentials( _request = build_beta_evaluators_get_credentials_request( name=name, version=version, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6384,9 +6185,6 @@ def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: A :rtype: ~azure.ai.projects.models.Insight :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6409,7 +6207,6 @@ def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: A _content = json.dumps(insight, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_insights_generate_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6436,7 +6233,11 @@ def generate(self, insight: Union[_models.Insight, JSON, IO[bytes]], **kwargs: A except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() @@ -6461,9 +6262,6 @@ def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, ** :rtype: ~azure.ai.projects.models.Insight :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6479,7 +6277,6 @@ def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, ** _request = build_beta_insights_get_request( insight_id=insight_id, - foundry_features=_foundry_features, include_coordinates=include_coordinates, api_version=self._config.api_version, headers=_headers, @@ -6505,7 +6302,11 @@ def get(self, insight_id: str, *, include_coordinates: Optional[bool] = None, ** except (StreamConsumedError, StreamClosedError): pass map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) if _stream: deserialized = response.iter_bytes() if _decompress else response.iter_raw() @@ -6546,9 +6347,6 @@ def list( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Insight] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -6566,7 +6364,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_insights_list_request( - foundry_features=_foundry_features, type=type, eval_id=eval_id, run_id=run_id, @@ -6597,7 +6394,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6629,7 +6426,11 @@ def get_next(next_link=None): if response.status_code not in [200]: map_error(status_code=response.status_code, response=response, error_map=error_map) - raise HttpResponseError(response=response) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) return pipeline_response @@ -6743,9 +6544,6 @@ def create( :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6775,7 +6573,6 @@ def create( _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_memory_stores_create_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6906,9 +6703,6 @@ def update( :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -6935,7 +6729,6 @@ def update( _request = build_beta_memory_stores_update_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -6988,9 +6781,6 @@ def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: :rtype: ~azure.ai.projects.models.MemoryStoreDetails :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7006,7 +6796,6 @@ def get(self, name: str, **kwargs: Any) -> _models.MemoryStoreDetails: _request = build_beta_memory_stores_get_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -7076,9 +6865,6 @@ def list( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.MemoryStoreDetails] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -7095,7 +6881,6 @@ def list( def prepare_request(_continuation_token=None): _request = build_beta_memory_stores_list_request( - foundry_features=_foundry_features, limit=limit, order=order, after=_continuation_token, @@ -7151,9 +6936,6 @@ def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreResult: :rtype: ~azure.ai.projects.models.DeleteMemoryStoreResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7169,7 +6951,6 @@ def delete(self, name: str, **kwargs: Any) -> _models.DeleteMemoryStoreResult: _request = build_beta_memory_stores_delete_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -7263,9 +7044,6 @@ def _search_memories( :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7299,7 +7077,6 @@ def _search_memories( _request = build_beta_memory_stores_search_memories_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -7353,9 +7130,6 @@ def _update_memories_initial( update_delay: Optional[int] = None, **kwargs: Any ) -> Iterator[bytes]: - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7389,7 +7163,6 @@ def _update_memories_initial( _request = build_beta_memory_stores_update_memories_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -7490,9 +7263,6 @@ def _begin_update_memories( ~azure.core.polling.LROPoller[~azure.ai.projects.models.MemoryStoreUpdateCompletedResult] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) _params = kwargs.pop("params", {}) or {} @@ -7505,7 +7275,6 @@ def _begin_update_memories( raw_result = self._update_memories_initial( name=name, body=body, - foundry_features=_foundry_features, scope=scope, items=items, previous_update_id=previous_update_id, @@ -7630,9 +7399,6 @@ def delete_scope( :rtype: ~azure.ai.projects.models.MemoryStoreDeleteScopeResult :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7661,7 +7427,6 @@ def delete_scope( _request = build_beta_memory_stores_delete_scope_request( name=name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -7732,9 +7497,6 @@ def get(self, name: str, **kwargs: Any) -> _models.RedTeam: :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7750,7 +7512,6 @@ def get(self, name: str, **kwargs: Any) -> _models.RedTeam: _request = build_beta_red_teams_get_request( name=name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -7795,9 +7556,6 @@ def list(self, **kwargs: Any) -> ItemPaged["_models.RedTeam"]: :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.RedTeam] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -7815,7 +7573,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_red_teams_list_request( - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -7841,7 +7598,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7934,9 +7691,6 @@ def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwargs: An :rtype: ~azure.ai.projects.models.RedTeam :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -7959,7 +7713,6 @@ def create(self, red_team: Union[_models.RedTeam, JSON, IO[bytes]], **kwargs: An _content = json.dumps(red_team, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_red_teams_create_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -8030,9 +7783,6 @@ def delete(self, schedule_id: str, **kwargs: Any) -> None: # pylint: disable=in :rtype: None :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8048,7 +7798,6 @@ def delete(self, schedule_id: str, **kwargs: Any) -> None: # pylint: disable=in _request = build_beta_schedules_delete_request( schedule_id=schedule_id, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -8082,9 +7831,6 @@ def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8100,7 +7846,6 @@ def get(self, schedule_id: str, **kwargs: Any) -> _models.Schedule: _request = build_beta_schedules_get_request( schedule_id=schedule_id, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -8156,9 +7901,6 @@ def list( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.Schedule] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -8176,7 +7918,6 @@ def prepare_request(next_link=None): if not next_link: _request = build_beta_schedules_list_request( - foundry_features=_foundry_features, type=type, enabled=enabled, api_version=self._config.api_version, @@ -8204,7 +7945,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -8311,9 +8052,6 @@ def create_or_update( :rtype: ~azure.ai.projects.models.Schedule :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8337,7 +8075,6 @@ def create_or_update( _request = build_beta_schedules_create_or_update_request( schedule_id=schedule_id, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -8388,9 +8125,6 @@ def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.Sched :rtype: ~azure.ai.projects.models.ScheduleRun :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8407,7 +8141,6 @@ def get_run(self, schedule_id: str, run_id: str, **kwargs: Any) -> _models.Sched _request = build_beta_schedules_get_run_request( schedule_id=schedule_id, run_id=run_id, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -8470,9 +8203,6 @@ def list_runs( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.ScheduleRun] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -8491,7 +8221,6 @@ def prepare_request(next_link=None): _request = build_beta_schedules_list_runs_request( schedule_id=schedule_id, - foundry_features=_foundry_features, type=type, enabled=enabled, api_version=self._config.api_version, @@ -8519,7 +8248,7 @@ def prepare_request(next_link=None): "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params, - headers={"Foundry-Features": _SERIALIZER.header("foundry_features", _foundry_features, "str")}, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -8663,9 +8392,6 @@ def create( :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8695,7 +8421,6 @@ def create( _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore _request = build_beta_toolsets_create_request( - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -8832,9 +8557,6 @@ def update( :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8863,7 +8585,6 @@ def update( _request = build_beta_toolsets_update_request( tool_set_name=tool_set_name, - foundry_features=_foundry_features, content_type=content_type, api_version=self._config.api_version, content=_content, @@ -8916,9 +8637,6 @@ def get(self, tool_set_name: str, **kwargs: Any) -> _models.ToolsetObject: :rtype: ~azure.ai.projects.models.ToolsetObject :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -8934,7 +8652,6 @@ def get(self, tool_set_name: str, **kwargs: Any) -> _models.ToolsetObject: _request = build_beta_toolsets_get_request( tool_set_name=tool_set_name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, @@ -9004,9 +8721,6 @@ def list( :rtype: ~azure.core.paging.ItemPaged[~azure.ai.projects.models.ToolsetObject] :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) _headers = kwargs.pop("headers", {}) or {} _params = kwargs.pop("params", {}) or {} @@ -9023,7 +8737,6 @@ def list( def prepare_request(_continuation_token=None): _request = build_beta_toolsets_list_request( - foundry_features=_foundry_features, limit=limit, order=order, after=_continuation_token, @@ -9079,9 +8792,6 @@ def delete(self, tool_set_name: str, **kwargs: Any) -> _models.DeleteToolsetResp :rtype: ~azure.ai.projects.models.DeleteToolsetResponse :raises ~azure.core.exceptions.HttpResponseError: """ - _foundry_features: Literal[_FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW] = ( - _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW - ) error_map: MutableMapping = { 401: ClientAuthenticationError, 404: ResourceNotFoundError, @@ -9097,7 +8807,6 @@ def delete(self, tool_set_name: str, **kwargs: Any) -> _models.DeleteToolsetResp _request = build_beta_toolsets_delete_request( tool_set_name=tool_set_name, - foundry_features=_foundry_features, api_version=self._config.api_version, headers=_headers, params=_params, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index cdba64dd7f84..2330bf816c59 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -8,11 +8,14 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import Any, List +from functools import wraps +import inspect +from typing import Any, Callable, List +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _BETA_OPERATION_FEATURE_HEADERS, _has_header_case_insensitive from ._patch_agents import AgentsOperations from ._patch_datasets import DatasetsOperations from ._patch_evaluation_rules import EvaluationRulesOperations -from ._patch_evaluators import EvaluatorsOperations as BetaEvaluatorsOperations +from ._patch_evaluators import BetaEvaluatorsOperations from ._patch_telemetry import TelemetryOperations from ._patch_connections import ConnectionsOperations from ._patch_memories import BetaMemoryStoresOperations @@ -26,6 +29,59 @@ ) +def _method_accepts_keyword_headers(method: Callable[..., Any]) -> bool: + try: + signature = inspect.signature(method) + except (TypeError, ValueError): + return False + + for parameter in signature.parameters.values(): + if parameter.name == "headers": + return True + if parameter.kind == inspect.Parameter.VAR_KEYWORD: + return True + + return False + + +class _OperationMethodHeaderProxy: + """Proxy that injects the Foundry-Features header into public operation method calls.""" + + def __init__(self, operation: Any, foundry_features_value: str): + object.__setattr__(self, "_operation", operation) + object.__setattr__(self, "_foundry_features_value", foundry_features_value) + + def __getattr__(self, name: str) -> Any: + attribute = getattr(self._operation, name) + + if name.startswith("_") or not callable(attribute) or not _method_accepts_keyword_headers(attribute): + return attribute + + @wraps(attribute) + def _wrapped(*args: Any, **kwargs: Any) -> Any: + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value} + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + try: + headers[_FOUNDRY_FEATURES_HEADER_NAME] = self._foundry_features_value + except Exception: # pylint: disable=broad-except + # Fall back to replacing invalid/immutable header containers. + kwargs["headers"] = { + _FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value, + } + + return attribute(*args, **kwargs) + + return _wrapped + + def __dir__(self) -> list: + return dir(self._operation) + + def __setattr__(self, name: str, value: Any) -> None: + setattr(self._operation, name, value) + + class BetaOperations(GeneratedBetaOperations): """ .. warning:: @@ -58,6 +114,13 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # Replace with patched class that includes begin_update_memories self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) + for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): + setattr( + self, + property_name, + _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), + ) + __all__: List[str] = [ "AgentsOperations", diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py index 6f2f3374d3f9..858cbdc359df 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py @@ -10,8 +10,11 @@ from typing import Union, Optional, Any, IO, overload, Final from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator import distributed_trace from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset from .. import models as _models +from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive """ Example service response payload when the caller is trying to use a feature preview without opt-in flag (service error 403 (Forbidden)): @@ -34,6 +37,13 @@ "when calling the AIProjectClient constructor. " "\nNote that preview features are under development and subject to change." ) +_AGENT_OPERATION_FEATURE_HEADERS: str = ",".join( + [ + _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, + _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, + _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, + ] +) class AgentsOperations(GeneratedAgentsOperations): @@ -135,6 +145,7 @@ def create_version( """ ... + @distributed_trace def create_version( self, agent_name: str, @@ -172,6 +183,16 @@ def create_version( :rtype: ~azure.ai.projects.models.AgentVersionDetails :raises ~azure.core.exceptions.HttpResponseError: """ + + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS + kwargs["headers"] = headers + try: return super().create_version( agent_name, diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py index 5c2ca412a468..19dcee1ed6bd 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py @@ -10,9 +10,12 @@ from typing import Union, Any, IO, overload from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator import distributed_trace from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE from .. import models as _models +from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): @@ -82,6 +85,7 @@ def create_or_update( """ ... + @distributed_trace def create_or_update( self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any ) -> _models.EvaluationRule: @@ -96,6 +100,18 @@ def create_or_update( :rtype: ~azure.ai.projects.models.EvaluationRule :raises ~azure.core.exceptions.HttpResponseError: """ + + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = { + _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + } + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + kwargs["headers"] = headers + try: return super().create_or_update(id, evaluation_rule, **kwargs) except HttpResponseError as exc: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py index 20a218ac6d49..ca1edda790b3 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -16,7 +16,9 @@ from azure.storage.blob import ContainerClient from azure.core.tracing.decorator import distributed_trace from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from ._operations import BetaEvaluatorsOperations as EvaluatorsOperationsGenerated, JSON +from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON +from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME from ..models._models import ( CodeBasedEvaluatorDefinition, EvaluatorVersion, @@ -24,8 +26,10 @@ logger = logging.getLogger(__name__) +_EVALUATORS_FOUNDRY_FEATURES_VALUE = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value -class EvaluatorsOperations(EvaluatorsOperationsGenerated): + +class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): """ .. warning:: **DO NOT** instantiate this class directly. @@ -139,6 +143,7 @@ def _start_pending_upload_and_get_container_client( name=name, version=version, pending_upload_request=request_body, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, ) # The service returns blobReferenceForConsumption @@ -173,7 +178,11 @@ def _get_next_version(self, name: str) -> str: :rtype: str """ try: - versions = list(self.list_versions(name=name)) + versions = list( + self.list_versions( + name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} + ) + ) if versions: numeric_versions = [] for v in versions: @@ -243,6 +252,7 @@ def upload( result = self.create_version( name=name, evaluator_version=evaluator_version, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, ) return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py index a946cfd11080..33b932900a35 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py @@ -21,8 +21,11 @@ MemoryStoreUpdateCompletedResult, UpdateMemoriesLROPoller, ) -from ..models._patch import _UpdateMemoriesLROPollingMethod -from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._patch import ( + _UpdateMemoriesLROPollingMethod, + _FOUNDRY_FEATURES_HEADER_NAME, + _BETA_OPERATION_FEATURE_HEADERS, +) from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations from .._validation import api_version_validation from .._utils.model_base import _deserialize, _serialize @@ -389,7 +392,7 @@ def get_long_running_output(pipeline_response): polling_method: _UpdateMemoriesLROPollingMethod = _UpdateMemoriesLROPollingMethod( lro_delay, path_format_arguments=path_format_arguments, - headers={"Foundry-Features": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value}, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, **kwargs, ) else: diff --git a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd index 885fc6b2348f..9092136930f6 100644 --- a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd +++ b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd @@ -10,8 +10,6 @@ REM REM Revert this, as we want to keep some edits to these file. git restore pyproject.toml -REM Looks like this is no longer needed: -REM git restore azure\ai\projects\_version.py REM Rename `"items_property": items`, to `"items": items` in search_memories and begin_update_memories methods. "items" is specified in TypeSpec, but Python emitter does not allow it. powershell -Command "(Get-Content azure\ai\projects\aio\operations\_operations.py) -replace '\"items_property\": items', '\"items\": items' | Set-Content azure\ai\projects\aio\operations\_operations.py" @@ -27,12 +25,13 @@ powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace REM Rename DEFAULT2024_11_15 to DEFAULT_2024_11_15 powershell -Command "(Get-Content azure\ai\projects\models\_enums.py) -replace 'DEFAULT2024_11_15', 'DEFAULT_2024_11_15' | Set-Content azure\ai\projects\models\_enums.py" -REM exit /b - -REM Remove required 'foundry_features' from public API surface, and instead set them internally in the relevant methods -copy agent-scripts\auto_set_foundry_features.py . -python auto_set_foundry_features.py -del auto_set_foundry_features.py +REM Edit both _operations.py files to fix missing Foundry-Features HTTP request header in continued list paging calls. Add: +REM headers=_headers +REM to the end of each of these lines in the BetaXxxOperations classes (do not do this in GA operations classes!) +REM "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params +REM In emitted code, these first 7 of those lines are associated with GA operations, so start the replacement +REM from the 8th occurrence onward. +powershell -Command "$gaCount=7; $old=[char]34+'GET'+[char]34+', urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params'; $new=$old+', headers=_headers'; foreach ($f in 'azure\ai\projects\aio\operations\_operations.py','azure\ai\projects\operations\_operations.py') { $c=Get-Content $f -Raw; $parts=$c -split [regex]::Escape($old); $r=$parts[0]; for ($i=1; $i -lt $parts.Length; $i++) { if ($i -le $gaCount) { $r+=$old+$parts[$i] } else { $r+=$new+$parts[$i] } }; Set-Content $f $r -NoNewline }" REM Finishing by running 'black' tool to format code. black --config ../../../eng/black-pyproject.toml . @@ -49,10 +48,4 @@ REM { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, REM { \"type\": \"image_generation\" } REM ]. Required.""" -REM Edit file azure/ai/projects/aio/operations/_operations.py and: -REM Add "_get_agent_definition_opt_in_keys," as the first line of: from ...operations._operations import ( -REM Add: -REM _SERIALIZER = Serializer() -REM _SERIALIZER.client_side_validation = False -REM just before the definition of the class BetaOperations (the first class defined in the file) diff --git a/sdk/ai/azure-ai-projects/pyproject.toml b/sdk/ai/azure-ai-projects/pyproject.toml index f5babde606a2..26eac48e1123 100644 --- a/sdk/ai/azure-ai-projects/pyproject.toml +++ b/sdk/ai/azure-ai-projects/pyproject.toml @@ -52,7 +52,6 @@ readme = {file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown"} [tool.setuptools.packages.find] exclude = [ - "agent-scripts*", "azure.ai", "azure", "doc*", diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py index 84c243fc0fa8..09e2279b2701 100644 --- a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload.py @@ -8,16 +8,19 @@ import pytest from unittest.mock import MagicMock, patch, call from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from azure.ai.projects.operations._patch_evaluators import EvaluatorsOperations +from azure.ai.projects.operations._patch_evaluators import BetaEvaluatorsOperations, _EVALUATORS_FOUNDRY_FEATURES_VALUE from azure.ai.projects.models import CodeBasedEvaluatorDefinition, EvaluatorVersion +from azure.ai.projects.models._patch import _FOUNDRY_FEATURES_HEADER_NAME + +_EVALUATORS_HEADERS = {_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} class TestEvaluatorsUpload: - """Unit tests for EvaluatorsOperations.upload() method.""" + """Unit tests for BetaEvaluatorsOperations.upload() method.""" def _create_operations(self): - """Create a mock EvaluatorsOperations instance with mocked service calls.""" - ops = object.__new__(EvaluatorsOperations) + """Create a mock BetaEvaluatorsOperations instance with mocked service calls.""" + ops = object.__new__(BetaEvaluatorsOperations) ops.pending_upload = MagicMock() ops.list_versions = MagicMock() ops.create_version = MagicMock() @@ -187,6 +190,7 @@ def test_start_pending_upload_passes_connection_name(self): name="test", version="1", pending_upload_request={"connectionName": "my-connection"}, + headers=_EVALUATORS_HEADERS, ) # --------------------------------------------------------------- @@ -372,6 +376,7 @@ def test_upload_calls_create_version_with_correct_args(self): ops.create_version.assert_called_once_with( name="my_eval", evaluator_version=evaluator_version, + headers=_EVALUATORS_HEADERS, ) assert result == {"name": "my_eval", "version": "1"} @@ -400,6 +405,7 @@ def test_upload_auto_increments_version(self): name="my_eval", version="3", pending_upload_request={}, + headers=_EVALUATORS_HEADERS, ) # --------------------------------------------------------------- diff --git a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py index ff85c791b2f2..5417331b6585 100644 --- a/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py +++ b/sdk/ai/azure-ai-projects/tests/evaluators/test_evaluators_upload_async.py @@ -8,16 +8,22 @@ import pytest from unittest.mock import AsyncMock, MagicMock, patch from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from azure.ai.projects.aio.operations._patch_evaluators_async import EvaluatorsOperations +from azure.ai.projects.aio.operations._patch_evaluators_async import ( + BetaEvaluatorsOperations, + _EVALUATORS_FOUNDRY_FEATURES_VALUE, +) from azure.ai.projects.models import CodeBasedEvaluatorDefinition, EvaluatorVersion +from azure.ai.projects.models._patch import _FOUNDRY_FEATURES_HEADER_NAME + +_EVALUATORS_HEADERS = {_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} class TestEvaluatorsUploadAsync: - """Unit tests for async EvaluatorsOperations.upload() method.""" + """Unit tests for async BetaEvaluatorsOperations.upload() method.""" def _create_operations(self): - """Create a mock async EvaluatorsOperations instance with mocked service calls.""" - ops = object.__new__(EvaluatorsOperations) + """Create a mock async BetaEvaluatorsOperations instance with mocked service calls.""" + ops = object.__new__(BetaEvaluatorsOperations) ops.pending_upload = AsyncMock() ops.list_versions = MagicMock() ops.create_version = AsyncMock() @@ -210,6 +216,7 @@ async def test_start_pending_upload_passes_connection_name(self): name="test", version="1", pending_upload_request={"connectionName": "my-connection"}, + headers=_EVALUATORS_HEADERS, ) # --------------------------------------------------------------- @@ -403,6 +410,7 @@ async def test_upload_calls_create_version_with_correct_args(self): ops.create_version.assert_called_once_with( name="my_eval", evaluator_version=evaluator_version, + headers=_EVALUATORS_HEADERS, ) assert result == {"name": "my_eval", "version": "1"} @@ -436,6 +444,7 @@ async def _version_gen(*args, **kwargs): name="my_eval", version="3", pending_upload_request={}, + headers=_EVALUATORS_HEADERS, ) # --------------------------------------------------------------- diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_beta_operations.py similarity index 55% rename from sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py rename to sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_beta_operations.py index b9890940a9da..ef64040de4ab 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_beta_operations.py @@ -40,9 +40,11 @@ from foundry_features_header_test_base import ( EXPECTED_FOUNDRY_FEATURES, FAKE_ENDPOINT, + FOUNDRY_FEATURES_HEADER, FakeCredential, FoundryFeaturesHeaderTestBase, _RequestCaptured, + _UNSET_SENTINELS, ) # --------------------------------------------------------------------------- @@ -104,11 +106,16 @@ def _discover_test_cases() -> list[pytest.param]: f"base_test.py." ) - for m_name in sorted(dir(sc)): + # Use the underlying operation's dir() for method discovery. + # The proxy may not implement __dir__ in older installs, causing dir(sc) to + # return no public methods. Methods are still fetched via getattr(sc, ...) so + # the header-injecting proxy wrapper is exercised. + _underlying_op = getattr(sc, "_operation", sc) + for m_name in sorted(dir(_underlying_op)): if m_name.startswith("_"): continue method = getattr(sc, m_name) - if not inspect.ismethod(method): + if not callable(method): continue label = f".beta.{sc_name}.{m_name}()" @@ -140,8 +147,8 @@ def client() -> Iterator[AIProjectClient]: def _print_report() -> Iterator[None]: """Print the full Foundry-Features header report after all tests finish.""" yield - report = TestFoundryFeaturesHeader._report - max_len = TestFoundryFeaturesHeader._report_max_label_len + report = TestFoundryFeaturesHeaderOnBetaOperations._report + max_len = TestFoundryFeaturesHeaderOnBetaOperations._report_max_label_len if report: print("\n\nFoundry-Features header report (sync):") for label, header_value in sorted(report): @@ -153,7 +160,7 @@ def _print_report() -> Iterator[None]: # --------------------------------------------------------------------------- -class TestFoundryFeaturesHeader(FoundryFeaturesHeaderTestBase): +class TestFoundryFeaturesHeaderOnBetaOperations(FoundryFeaturesHeaderTestBase): """Sync tests: assert every public .beta method sends the Foundry-Features header.""" _report: ClassVar[List[Tuple[str, str]]] = [] @@ -197,7 +204,7 @@ def _assert_header(cls, label: str, call: Any, expected_value: str) -> str: # ------------------------------------------------------------------ @pytest.mark.parametrize("label,subclient_name,method_name,expected_header_value", _TEST_CASES) - def test_foundry_features_header( + def test_foundry_features_header_on_beta_operations( self, client: AIProjectClient, label: str, @@ -209,3 +216,100 @@ def test_foundry_features_header( sc = getattr(client.beta, subclient_name) method = getattr(sc, method_name) self._assert_header(label, self._make_fake_call(method), expected_header_value) + + +# --------------------------------------------------------------------------- +# Pick the first discovered beta method (reuses _TEST_CASES, no extra I/O) +# --------------------------------------------------------------------------- + +_, _FIRST_SC_NAME, _FIRST_M_NAME, _ = _TEST_CASES[0].values + + +# --------------------------------------------------------------------------- +# Tests: caller-controlled header override / augmentation behaviour +# --------------------------------------------------------------------------- + + +class TestFoundryFeaturesHeaderOverrideOnBetaOperations(FoundryFeaturesHeaderTestBase): + """Verify callers can override or augment the internally-set Foundry-Features header. + + All three tests operate on the first public method enumerated on any .beta sub-client + to avoid hard-coding a specific API surface. + """ + + @staticmethod + def _capture(call: Any) -> Any: + """Invoke *call* and return the captured HttpRequest (sync version).""" + try: + result = call() + except _RequestCaptured as exc: + return exc.request + try: + next(iter(result)) + except _RequestCaptured as exc: + return exc.request + except StopIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + raise AssertionError("Transport was never called") + + @staticmethod + def _make_fake_call_with_headers(method: Any, headers: dict) -> Any: + """Like _make_fake_call but also passes *headers* as a keyword argument.""" + sig = inspect.signature(method) + args: list[Any] = [] + kwargs: dict[str, Any] = {"headers": headers} + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD): + continue + is_required = param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS + if not is_required: + continue + fake = FoundryFeaturesHeaderTestBase._fake_for_param(param) + if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY): + args.append(fake) + else: + kwargs[param_name] = fake + return lambda: method(*args, **kwargs) + + def test_foundry_features_header_override_on_beta_operations(self, client: AIProjectClient) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue"} must reach the transport + instead of the internally-set default value.""" + sc = getattr(client.beta, _FIRST_SC_NAME) + method = getattr(sc, _FIRST_M_NAME) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + + def test_foundry_features_header_override_and_add_on_beta_operations(self, client: AIProjectClient) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + must result in both headers reaching the transport (custom Foundry-Features value and extra header).""" + sc = getattr(client.beta, _FIRST_SC_NAME) + method = getattr(sc, _FIRST_M_NAME) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + + def test_foundry_features_header_additional_header_on_beta_operations(self, client: AIProjectClient) -> None: + """Caller-supplied headers={"SomeOtherHeaderName": "SomeOtherHeaderValue"} (no Foundry-Features + override) must result in the extra header AND the internally-set Foundry-Features header both + reaching the transport.""" + sc = getattr(client.beta, _FIRST_SC_NAME) + method = getattr(sc, _FIRST_M_NAME) + custom_headers = {"SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert request.headers.get(FOUNDRY_FEATURES_HEADER) is not None, ( + f"Expected '{FOUNDRY_FEATURES_HEADER}' to be present but it was missing. " + f"Headers: {dict(request.headers)}" + ) + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_beta_operations_async.py similarity index 57% rename from sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py rename to sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_beta_operations_async.py index 9cadf09a6910..448ed33ef740 100644 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_async.py +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_beta_operations_async.py @@ -42,9 +42,11 @@ from foundry_features_header_test_base import ( EXPECTED_FOUNDRY_FEATURES, FAKE_ENDPOINT, + FOUNDRY_FEATURES_HEADER, AsyncFakeCredential, FoundryFeaturesHeaderTestBase, _RequestCaptured, + _UNSET_SENTINELS, ) # --------------------------------------------------------------------------- @@ -109,11 +111,16 @@ def _discover_async_test_cases() -> list[pytest.param]: f"base_test.py." ) - for m_name in sorted(dir(sc)): + # Use the underlying operation's dir() for method discovery. + # The proxy may not implement __dir__ in older installs, causing dir(sc) to + # return no public methods. Methods are still fetched via getattr(sc, ...) so + # the header-injecting proxy wrapper is exercised. + _underlying_op = getattr(sc, "_operation", sc) + for m_name in sorted(dir(_underlying_op)): if m_name.startswith("_"): continue method = getattr(sc, m_name) - if not inspect.ismethod(method): + if not callable(method): continue label = f".beta.{sc_name}.{m_name}() [async]" @@ -150,8 +157,8 @@ def async_client() -> Iterator[AsyncAIProjectClient]: def _print_report_async() -> Iterator[None]: """Print the full Foundry-Features header report after all async tests finish.""" yield - report = TestFoundryFeaturesHeaderAsync._report - max_len = TestFoundryFeaturesHeaderAsync._report_max_label_len + report = TestFoundryFeaturesHeaderOnBetaOperationsAsync._report + max_len = TestFoundryFeaturesHeaderOnBetaOperationsAsync._report_max_label_len if report: print("\n\nFoundry-Features header report (async):") for label, header_value in sorted(report): @@ -163,7 +170,7 @@ def _print_report_async() -> Iterator[None]: # --------------------------------------------------------------------------- -class TestFoundryFeaturesHeaderAsync(FoundryFeaturesHeaderTestBase): +class TestFoundryFeaturesHeaderOnBetaOperationsAsync(FoundryFeaturesHeaderTestBase): """Async tests: assert every public async .beta method sends the Foundry-Features header.""" _report: ClassVar[List[Tuple[str, str]]] = [] @@ -217,7 +224,7 @@ async def _assert_header_async(cls, label: str, call: Any, expected_value: str) @pytest.mark.asyncio @pytest.mark.parametrize("label,subclient_name,method_name,expected_header_value", _ASYNC_TEST_CASES) - async def test_foundry_features_header_async( + async def test_foundry_features_header_on_beta_operations_async( self, async_client: AsyncAIProjectClient, label: str, @@ -229,3 +236,113 @@ async def test_foundry_features_header_async( sc = getattr(async_client.beta, subclient_name) method = getattr(sc, method_name) await self._assert_header_async(label, self._make_fake_call(method), expected_header_value) + + +# --------------------------------------------------------------------------- +# Pick the first discovered beta method (reuses _ASYNC_TEST_CASES, no extra I/O) +# --------------------------------------------------------------------------- + +_, _FIRST_SC_NAME, _FIRST_M_NAME, _ = _ASYNC_TEST_CASES[0].values + + +# --------------------------------------------------------------------------- +# Tests: caller-controlled header override / augmentation behaviour (async) +# --------------------------------------------------------------------------- + + +class TestFoundryFeaturesHeaderOverrideOnBetaOperationsAsync(FoundryFeaturesHeaderTestBase): + """Verify callers can override or augment the internally-set Foundry-Features header (async). + + All three tests operate on the first public method enumerated on any .beta sub-client + to avoid hard-coding a specific API surface. + """ + + @staticmethod + async def _capture_async(call: Any) -> Any: + """Invoke *call* and return the captured HttpRequest (async version).""" + result = call() + if inspect.isawaitable(result): + try: + await result + except _RequestCaptured as exc: + return exc.request + raise AssertionError("Transport was never called (awaitable completed without raising)") + ai = result.__aiter__() + try: + await ai.__anext__() + except _RequestCaptured as exc: + return exc.request + except StopAsyncIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + raise AssertionError("Transport was never called") + + @staticmethod + def _make_fake_call_with_headers(method: Any, headers: dict) -> Any: + """Like _make_fake_call but also passes *headers* as a keyword argument.""" + sig = inspect.signature(method) + args: list[Any] = [] + kwargs: dict[str, Any] = {"headers": headers} + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD): + continue + is_required = param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS + if not is_required: + continue + fake = FoundryFeaturesHeaderTestBase._fake_for_param(param) + if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY): + args.append(fake) + else: + kwargs[param_name] = fake + return lambda: method(*args, **kwargs) + + @pytest.mark.asyncio + async def test_foundry_features_header_override_on_beta_operations_async( + self, async_client: AsyncAIProjectClient + ) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue"} must reach the transport + instead of the internally-set default value.""" + sc = getattr(async_client.beta, _FIRST_SC_NAME) + method = getattr(sc, _FIRST_M_NAME) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + + @pytest.mark.asyncio + async def test_foundry_features_header_override_and_add_on_beta_operations_async( + self, async_client: AsyncAIProjectClient + ) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + must result in both headers reaching the transport (custom Foundry-Features value and extra header).""" + sc = getattr(async_client.beta, _FIRST_SC_NAME) + method = getattr(sc, _FIRST_M_NAME) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + + @pytest.mark.asyncio + async def test_foundry_features_header_additional_header_on_beta_operations_async( + self, async_client: AsyncAIProjectClient + ) -> None: + """Caller-supplied headers={"SomeOtherHeaderName": "SomeOtherHeaderValue"} (no Foundry-Features + override) must result in the extra header AND the internally-set Foundry-Features header both + reaching the transport.""" + sc = getattr(async_client.beta, _FIRST_SC_NAME) + method = getattr(sc, _FIRST_M_NAME) + custom_headers = {"SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert request.headers.get(FOUNDRY_FEATURES_HEADER) is not None, ( + f"Expected '{FOUNDRY_FEATURES_HEADER}' to be present but it was missing. " + f"Headers: {dict(request.headers)}" + ) + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations.py new file mode 100644 index 000000000000..bc11af8db927 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations.py @@ -0,0 +1,298 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Tests optional Foundry-Features header behavior on non-beta sync methods. + +These tests hard-code the non-beta methods that optionally send the +Foundry-Features header and verify both modes: + - allow_preview=True -> header is present with expected value + - allow_preview unset -> header is absent +""" + +import inspect +from typing import Any, ClassVar, Iterator, List, Tuple + +import pytest +from azure.core.pipeline.transport import HttpTransport +from azure.ai.projects import AIProjectClient + +from foundry_features_header_test_base import ( + FAKE_ENDPOINT, + FOUNDRY_FEATURES_HEADER, + FakeCredential, + FoundryFeaturesHeaderTestBase, + _NON_BETA_OPTIONAL_TEST_CASES, + _RequestCaptured, + _UNSET_SENTINELS, +) + + +class CapturingTransport(HttpTransport): + """Sync transport that captures the outgoing request and raises _RequestCaptured.""" + + def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] + raise _RequestCaptured(request) + + def open(self) -> None: + pass + + def close(self) -> None: + pass + + def __enter__(self) -> "CapturingTransport": + return self + + def __exit__(self, *args: Any) -> None: + pass + + +@pytest.fixture(scope="module") +def client_preview_enabled() -> Iterator[AIProjectClient]: + with AIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=FakeCredential(), # type: ignore[arg-type] + allow_preview=True, + transport=CapturingTransport(), + ) as c: + yield c + + +@pytest.fixture(scope="module") +def client_preview_disabled() -> Iterator[AIProjectClient]: + with AIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=FakeCredential(), # type: ignore[arg-type] + transport=CapturingTransport(), + ) as c: + yield c + + +@pytest.fixture(scope="module", autouse=True) +def _print_report_optional() -> Iterator[None]: + """Print two Foundry-Features reports after all sync optional tests finish: + one for the allow_preview=True test and one for the allow_preview-unset test. + """ + yield + present_report = TestFoundryFeaturesHeaderOnGaOperations._report + if present_report: + max_len = TestFoundryFeaturesHeaderOnGaOperations._report_max_label_len + print( + "\n\nFoundry-Features header report on GA operations (sync) — test_foundry_features_header_present_on_ga_operations_when_preview_enabled:" + ) + for label, header_value in sorted(present_report): + print(f'{label:<{max_len}} | "{header_value}"') + + absent_report = TestFoundryFeaturesHeaderOnGaOperations._report_absent + if absent_report: + max_len = TestFoundryFeaturesHeaderOnGaOperations._report_absent_max_label_len + print( + "\n\nFoundry-Features header report on GA operations (sync) — test_foundry_features_header_absent_on_ga_operations_when_preview_not_enabled:" + ) + for label, header_value in sorted(absent_report): + print(f'{label:<{max_len}} | "{header_value}"') + + +class TestFoundryFeaturesHeaderOnGaOperations(FoundryFeaturesHeaderTestBase): + """Sync tests for optional Foundry-Features header behavior on non-beta methods.""" + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + _report_absent: ClassVar[List[Tuple[str, str]]] = [] + _report_absent_max_label_len: ClassVar[int] = 0 + + @staticmethod + def _capture(call: Any) -> Any: + """Call *call()* and return the captured HttpRequest.""" + try: + result = call() + except _RequestCaptured as exc: + return exc.request + + try: + next(iter(result)) + except _RequestCaptured as exc: + return exc.request + except StopIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + + raise AssertionError("Transport was never called") + + @classmethod + def _assert_header_present(cls, label: str, call: Any, expected_value: str) -> None: + request = cls._capture(call) + cls._record_header_assertion(label, request, expected_value) + + @classmethod + def _assert_header_absent(cls, label: str, call: Any) -> None: + request = cls._capture(call) + cls._record_header_absence_assertion(label, request) + + @pytest.mark.parametrize("method_name,expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_foundry_features_header_present_on_ga_operations_when_preview_enabled( + self, + client_preview_enabled: AIProjectClient, + method_name: str, + expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + self._assert_header_present(method_name, self._make_fake_call(method), expected_header_value) + + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_foundry_features_header_absent_on_ga_operations_when_preview_not_enabled( + self, + client_preview_disabled: AIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_disabled, subclient_name) + method = getattr(sc, method_attr) + self._assert_header_absent(method_name, self._make_fake_call(method)) + + +# --------------------------------------------------------------------------- +# Tests: caller-controlled header override / augmentation on GA operations +# (only applies when allow_preview=True, since that is when the header is sent) +# --------------------------------------------------------------------------- + + +class TestFoundryFeaturesHeaderOverrideOnGaOperations(FoundryFeaturesHeaderTestBase): + """Verify caller-supplied headers are correctly handled on GA (non-beta) operations. + + All tests are parametrized over _NON_BETA_OPTIONAL_TEST_CASES. + + Tests with allow_preview=True (header is injected internally): + - override: caller supplies Foundry-Features → custom value wins. + - override-and-add: caller supplies Foundry-Features + extra header → both reach transport. + - additional-header: caller supplies only an extra header → extra header AND the + internally-set Foundry-Features header both reach the transport. + + Tests without allow_preview (header is NOT injected internally): + - additional-header-no-preview: caller supplies only an extra header → that header + reaches the transport (Foundry-Features is absent, as expected). + """ + + @staticmethod + def _capture(call: Any) -> Any: + """Invoke *call* and return the captured HttpRequest (sync version).""" + try: + result = call() + except _RequestCaptured as exc: + return exc.request + try: + next(iter(result)) + except _RequestCaptured as exc: + return exc.request + except StopIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + raise AssertionError("Transport was never called") + + @staticmethod + def _make_fake_call_with_headers(method: Any, headers: dict) -> Any: + """Like _make_fake_call but also passes *headers* as a keyword argument.""" + sig = inspect.signature(method) + args: list[Any] = [] + kwargs: dict[str, Any] = {"headers": headers} + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD): + continue + is_required = param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS + if not is_required: + continue + fake = FoundryFeaturesHeaderTestBase._fake_for_param(param) + if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY): + args.append(fake) + else: + kwargs[param_name] = fake + return lambda: method(*args, **kwargs) + + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_foundry_features_header_override_on_ga_operations( + self, + client_preview_enabled: AIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue"} must reach the transport + instead of the internally-set default value (allow_preview=True).""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_foundry_features_header_override_and_add_on_ga_operations( + self, + client_preview_enabled: AIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + must result in both headers reaching the transport (allow_preview=True).""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"{method_name}: Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_foundry_features_header_additional_header_on_ga_operations( + self, + client_preview_enabled: AIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """Caller-supplied headers={"SomeOtherHeaderName": "SomeOtherHeaderValue"} (no Foundry-Features + override) must result in the extra header AND the internally-set Foundry-Features header both + reaching the transport (allow_preview=True).""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {"SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert request.headers.get(FOUNDRY_FEATURES_HEADER) is not None, ( + f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}' to be present but it was missing. " + f"Headers: {dict(request.headers)}" + ) + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"{method_name}: Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + def test_foundry_features_header_additional_header_on_ga_operations_no_preview( + self, + client_preview_disabled: AIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """When allow_preview is NOT set, a caller-supplied extra header + (e.g. headers={"SomeOtherHeaderName": "SomeOtherHeaderValue"}) must still reach + the transport. Foundry-Features is expected to be absent in this mode.""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(client_preview_disabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {"SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = self._capture(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"{method_name}: Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + assert request.headers.get(FOUNDRY_FEATURES_HEADER) is None, ( + f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}' to be absent when allow_preview is not set, " + f"but found: {request.headers.get(FOUNDRY_FEATURES_HEADER)}" + ) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations_async.py new file mode 100644 index 000000000000..05fa75a2dca6 --- /dev/null +++ b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_on_ga_operations_async.py @@ -0,0 +1,310 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Tests optional Foundry-Features header behavior on non-beta async methods. + +These tests hard-code the non-beta async methods that optionally send the +Foundry-Features header and verify both modes: + - allow_preview=True -> header is present with expected value + - allow_preview unset -> header is absent +""" + +import inspect +from typing import Any, ClassVar, Iterator, List, Tuple + +import pytest +from azure.core.pipeline.transport import AsyncHttpTransport +from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient + +from foundry_features_header_test_base import ( + FAKE_ENDPOINT, + FOUNDRY_FEATURES_HEADER, + AsyncFakeCredential, + FoundryFeaturesHeaderTestBase, + _NON_BETA_OPTIONAL_TEST_CASES, + _RequestCaptured, + _UNSET_SENTINELS, +) + + +class CapturingAsyncTransport(AsyncHttpTransport): + """Async transport that captures the outgoing request and raises _RequestCaptured.""" + + async def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] + raise _RequestCaptured(request) + + async def open(self) -> None: + pass + + async def close(self) -> None: + pass + + async def __aenter__(self) -> "CapturingAsyncTransport": + return self + + async def __aexit__(self, *args: Any) -> None: + pass + + +@pytest.fixture(scope="module") +def async_client_preview_enabled() -> Iterator[AsyncAIProjectClient]: + yield AsyncAIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=AsyncFakeCredential(), # type: ignore[arg-type] + allow_preview=True, + transport=CapturingAsyncTransport(), + ) + + +@pytest.fixture(scope="module") +def async_client_preview_disabled() -> Iterator[AsyncAIProjectClient]: + yield AsyncAIProjectClient( + endpoint=FAKE_ENDPOINT, + credential=AsyncFakeCredential(), # type: ignore[arg-type] + transport=CapturingAsyncTransport(), + ) + + +@pytest.fixture(scope="module", autouse=True) +def _print_report_optional_async() -> Iterator[None]: + """Print two Foundry-Features reports after all async optional tests finish: + one for the allow_preview=True test and one for the allow_preview-unset test. + """ + yield + present_report = TestFoundryFeaturesHeaderOnGaOperationsAsync._report + if present_report: + max_len = TestFoundryFeaturesHeaderOnGaOperationsAsync._report_max_label_len + print( + "\n\nFoundry-Features header report on GA operations (async) — test_foundry_features_header_present_on_ga_operations_when_preview_enabled_async:" + ) + for label, header_value in sorted(present_report): + print(f'{label:<{max_len}} | "{header_value}"') + + absent_report = TestFoundryFeaturesHeaderOnGaOperationsAsync._report_absent + if absent_report: + max_len = TestFoundryFeaturesHeaderOnGaOperationsAsync._report_absent_max_label_len + print( + "\n\nFoundry-Features header report on GA operations (async) — test_foundry_features_header_absent_on_ga_operations_when_preview_not_enabled_async:" + ) + for label, header_value in sorted(absent_report): + print(f'{label:<{max_len}} | "{header_value}"') + + +class TestFoundryFeaturesHeaderOnGaOperationsAsync(FoundryFeaturesHeaderTestBase): + """Async tests for optional Foundry-Features header behavior on non-beta methods.""" + + _report: ClassVar[List[Tuple[str, str]]] = [] + _report_max_label_len: ClassVar[int] = 0 + _report_absent: ClassVar[List[Tuple[str, str]]] = [] + _report_absent_max_label_len: ClassVar[int] = 0 + + @staticmethod + async def _capture_async(call: Any) -> Any: + """Invoke *call()* and return the captured HttpRequest.""" + result = call() + + if inspect.isawaitable(result): + try: + await result + except _RequestCaptured as exc: + return exc.request + raise AssertionError("Transport was never called (awaitable completed without raising)") + + ai = result.__aiter__() + try: + await ai.__anext__() + except _RequestCaptured as exc: + return exc.request + except StopAsyncIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + + raise AssertionError("Transport was never called") + + @classmethod + async def _assert_header_present_async(cls, label: str, call: Any, expected_value: str) -> None: + request = await cls._capture_async(call) + cls._record_header_assertion(label, request, expected_value) + + @classmethod + async def _assert_header_absent_async(cls, label: str, call: Any) -> None: + request = await cls._capture_async(call) + cls._record_header_absence_assertion(label, request) + + @pytest.mark.asyncio + @pytest.mark.parametrize("method_name,expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + async def test_foundry_features_header_present_on_ga_operations_when_preview_enabled_async( + self, + async_client_preview_enabled: AsyncAIProjectClient, + method_name: str, + expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + await self._assert_header_present_async(method_name, self._make_fake_call(method), expected_header_value) + + @pytest.mark.asyncio + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + async def test_foundry_features_header_absent_on_ga_operations_when_preview_not_enabled_async( + self, + async_client_preview_disabled: AsyncAIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_disabled, subclient_name) + method = getattr(sc, method_attr) + await self._assert_header_absent_async(method_name, self._make_fake_call(method)) + + +# --------------------------------------------------------------------------- +# Tests: caller-controlled header override / augmentation on GA operations (async) +# --------------------------------------------------------------------------- + + +class TestFoundryFeaturesHeaderOverrideOnGaOperationsAsync(FoundryFeaturesHeaderTestBase): + """Verify caller-supplied headers are correctly handled on GA (non-beta) async operations. + + All tests are parametrized over _NON_BETA_OPTIONAL_TEST_CASES. + + Tests with allow_preview=True (header is injected internally): + - override: caller supplies Foundry-Features → custom value wins. + - override-and-add: caller supplies Foundry-Features + extra header → both reach transport. + - additional-header: caller supplies only an extra header → extra header AND the + internally-set Foundry-Features header both reach the transport. + + Tests without allow_preview (header is NOT injected internally): + - additional-header-no-preview: caller supplies only an extra header → that header + reaches the transport (Foundry-Features is absent, as expected). + """ + + @staticmethod + async def _capture_async(call: Any) -> Any: + """Invoke *call* and return the captured HttpRequest (async version).""" + result = call() + if inspect.isawaitable(result): + try: + await result + except _RequestCaptured as exc: + return exc.request + raise AssertionError("Transport was never called (awaitable completed without raising)") + ai = result.__aiter__() + try: + await ai.__anext__() + except _RequestCaptured as exc: + return exc.request + except StopAsyncIteration: + raise AssertionError("Iterator exhausted without the transport being called") from None + raise AssertionError("Transport was never called") + + @staticmethod + def _make_fake_call_with_headers(method: Any, headers: dict) -> Any: + """Like _make_fake_call but also passes *headers* as a keyword argument.""" + sig = inspect.signature(method) + args: list[Any] = [] + kwargs: dict[str, Any] = {"headers": headers} + for param_name, param in sig.parameters.items(): + if param_name in ("self", "cls"): + continue + if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD): + continue + is_required = param.default is inspect.Parameter.empty or param.default in _UNSET_SENTINELS + if not is_required: + continue + fake = FoundryFeaturesHeaderTestBase._fake_for_param(param) + if param.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, inspect.Parameter.POSITIONAL_ONLY): + args.append(fake) + else: + kwargs[param_name] = fake + return lambda: method(*args, **kwargs) + + @pytest.mark.asyncio + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + async def test_foundry_features_header_override_on_ga_operations_async( + self, + async_client_preview_enabled: AsyncAIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue"} must reach the transport + instead of the internally-set default value (allow_preview=True).""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + + @pytest.mark.asyncio + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + async def test_foundry_features_header_override_and_add_on_ga_operations_async( + self, + async_client_preview_enabled: AsyncAIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """Caller-supplied headers={"Foundry-Features": "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + must result in both headers reaching the transport (allow_preview=True).""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {FOUNDRY_FEATURES_HEADER: "CustomValue", "SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get(FOUNDRY_FEATURES_HEADER) == "CustomValue" + ), f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}: CustomValue' but got: {dict(request.headers)}" + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"{method_name}: Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + + @pytest.mark.asyncio + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + async def test_foundry_features_header_additional_header_on_ga_operations_async( + self, + async_client_preview_enabled: AsyncAIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """Caller-supplied headers={"SomeOtherHeaderName": "SomeOtherHeaderValue"} (no Foundry-Features + override) must result in the extra header AND the internally-set Foundry-Features header both + reaching the transport (allow_preview=True).""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_enabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {"SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert request.headers.get(FOUNDRY_FEATURES_HEADER) is not None, ( + f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}' to be present but it was missing. " + f"Headers: {dict(request.headers)}" + ) + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"{method_name}: Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + + @pytest.mark.asyncio + @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) + async def test_foundry_features_header_additional_header_on_ga_operations_no_preview_async( + self, + async_client_preview_disabled: AsyncAIProjectClient, + method_name: str, + _expected_header_value: str, + ) -> None: + """When allow_preview is NOT set, a caller-supplied extra header + (e.g. headers={"SomeOtherHeaderName": "SomeOtherHeaderValue"}) must still reach + the transport. Foundry-Features is expected to be absent in this mode.""" + subclient_name, method_attr = method_name.split(".") + sc = getattr(async_client_preview_disabled, subclient_name) + method = getattr(sc, method_attr) + custom_headers = {"SomeOtherHeaderName": "SomeOtherHeaderValue"} + request = await self._capture_async(self._make_fake_call_with_headers(method, custom_headers)) + assert ( + request.headers.get("SomeOtherHeaderName") == "SomeOtherHeaderValue" + ), f"{method_name}: Expected 'SomeOtherHeaderName: SomeOtherHeaderValue' in headers but got: {dict(request.headers)}" + assert request.headers.get(FOUNDRY_FEATURES_HEADER) is None, ( + f"{method_name}: Expected '{FOUNDRY_FEATURES_HEADER}' to be absent when allow_preview is not set, " + f"but found: {request.headers.get(FOUNDRY_FEATURES_HEADER)}" + ) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py deleted file mode 100644 index b4d51f49842c..000000000000 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional.py +++ /dev/null @@ -1,149 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Tests optional Foundry-Features header behavior on non-beta sync methods. - -These tests hard-code the non-beta methods that optionally send the -Foundry-Features header and verify both modes: - - allow_preview=True -> header is present with expected value - - allow_preview unset -> header is absent -""" - -from typing import Any, ClassVar, Iterator, List, Tuple - -import pytest -from azure.core.pipeline.transport import HttpTransport -from azure.ai.projects import AIProjectClient - -from foundry_features_header_test_base import ( - FAKE_ENDPOINT, - FakeCredential, - FoundryFeaturesHeaderTestBase, - _NON_BETA_OPTIONAL_TEST_CASES, - _RequestCaptured, -) - - -class CapturingTransport(HttpTransport): - """Sync transport that captures the outgoing request and raises _RequestCaptured.""" - - def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] - raise _RequestCaptured(request) - - def open(self) -> None: - pass - - def close(self) -> None: - pass - - def __enter__(self) -> "CapturingTransport": - return self - - def __exit__(self, *args: Any) -> None: - pass - - -@pytest.fixture(scope="module") -def client_preview_enabled() -> Iterator[AIProjectClient]: - with AIProjectClient( - endpoint=FAKE_ENDPOINT, - credential=FakeCredential(), # type: ignore[arg-type] - allow_preview=True, - transport=CapturingTransport(), - ) as c: - yield c - - -@pytest.fixture(scope="module") -def client_preview_disabled() -> Iterator[AIProjectClient]: - with AIProjectClient( - endpoint=FAKE_ENDPOINT, - credential=FakeCredential(), # type: ignore[arg-type] - transport=CapturingTransport(), - ) as c: - yield c - - -@pytest.fixture(scope="module", autouse=True) -def _print_report_optional() -> Iterator[None]: - """Print two Foundry-Features reports after all sync optional tests finish: - one for the allow_preview=True test and one for the allow_preview-unset test. - """ - yield - present_report = TestFoundryFeaturesHeaderOptional._report - if present_report: - max_len = TestFoundryFeaturesHeaderOptional._report_max_label_len - print("\n\nFoundry-Features optional header report (sync) — test_optional_header_present_when_preview_enabled:") - for label, header_value in sorted(present_report): - print(f'{label:<{max_len}} | "{header_value}"') - - absent_report = TestFoundryFeaturesHeaderOptional._report_absent - if absent_report: - max_len = TestFoundryFeaturesHeaderOptional._report_absent_max_label_len - print( - "\n\nFoundry-Features optional header report (sync) — test_optional_header_absent_when_preview_not_enabled:" - ) - for label, header_value in sorted(absent_report): - print(f'{label:<{max_len}} | "{header_value}"') - - -class TestFoundryFeaturesHeaderOptional(FoundryFeaturesHeaderTestBase): - """Sync tests for optional Foundry-Features header behavior on non-beta methods.""" - - _report: ClassVar[List[Tuple[str, str]]] = [] - _report_max_label_len: ClassVar[int] = 0 - _report_absent: ClassVar[List[Tuple[str, str]]] = [] - _report_absent_max_label_len: ClassVar[int] = 0 - - @staticmethod - def _capture(call: Any) -> Any: - """Call *call()* and return the captured HttpRequest.""" - try: - result = call() - except _RequestCaptured as exc: - return exc.request - - try: - next(iter(result)) - except _RequestCaptured as exc: - return exc.request - except StopIteration: - raise AssertionError("Iterator exhausted without the transport being called") from None - - raise AssertionError("Transport was never called") - - @classmethod - def _assert_header_present(cls, label: str, call: Any, expected_value: str) -> None: - request = cls._capture(call) - cls._record_header_assertion(label, request, expected_value) - - @classmethod - def _assert_header_absent(cls, label: str, call: Any) -> None: - request = cls._capture(call) - cls._record_header_absence_assertion(label, request) - - @pytest.mark.parametrize("method_name,expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) - def test_optional_header_present_when_preview_enabled( - self, - client_preview_enabled: AIProjectClient, - method_name: str, - expected_header_value: str, - ) -> None: - subclient_name, method_attr = method_name.split(".") - sc = getattr(client_preview_enabled, subclient_name) - method = getattr(sc, method_attr) - self._assert_header_present(method_name, self._make_fake_call(method), expected_header_value) - - @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) - def test_optional_header_absent_when_preview_not_enabled( - self, - client_preview_disabled: AIProjectClient, - method_name: str, - _expected_header_value: str, - ) -> None: - subclient_name, method_attr = method_name.split(".") - sc = getattr(client_preview_disabled, subclient_name) - method = getattr(sc, method_attr) - self._assert_header_absent(method_name, self._make_fake_call(method)) diff --git a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py b/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py deleted file mode 100644 index 711af5221896..000000000000 --- a/sdk/ai/azure-ai-projects/tests/foundry_features_header/test_foundry_features_header_optional_async.py +++ /dev/null @@ -1,157 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Tests optional Foundry-Features header behavior on non-beta async methods. - -These tests hard-code the non-beta async methods that optionally send the -Foundry-Features header and verify both modes: - - allow_preview=True -> header is present with expected value - - allow_preview unset -> header is absent -""" - -import inspect -from typing import Any, ClassVar, Iterator, List, Tuple - -import pytest -from azure.core.pipeline.transport import AsyncHttpTransport -from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient - -from foundry_features_header_test_base import ( - FAKE_ENDPOINT, - AsyncFakeCredential, - FoundryFeaturesHeaderTestBase, - _NON_BETA_OPTIONAL_TEST_CASES, - _RequestCaptured, -) - - -class CapturingAsyncTransport(AsyncHttpTransport): - """Async transport that captures the outgoing request and raises _RequestCaptured.""" - - async def send(self, request: Any, **kwargs: Any) -> Any: # type: ignore[override] - raise _RequestCaptured(request) - - async def open(self) -> None: - pass - - async def close(self) -> None: - pass - - async def __aenter__(self) -> "CapturingAsyncTransport": - return self - - async def __aexit__(self, *args: Any) -> None: - pass - - -@pytest.fixture(scope="module") -def async_client_preview_enabled() -> Iterator[AsyncAIProjectClient]: - yield AsyncAIProjectClient( - endpoint=FAKE_ENDPOINT, - credential=AsyncFakeCredential(), # type: ignore[arg-type] - allow_preview=True, - transport=CapturingAsyncTransport(), - ) - - -@pytest.fixture(scope="module") -def async_client_preview_disabled() -> Iterator[AsyncAIProjectClient]: - yield AsyncAIProjectClient( - endpoint=FAKE_ENDPOINT, - credential=AsyncFakeCredential(), # type: ignore[arg-type] - transport=CapturingAsyncTransport(), - ) - - -@pytest.fixture(scope="module", autouse=True) -def _print_report_optional_async() -> Iterator[None]: - """Print two Foundry-Features reports after all async optional tests finish: - one for the allow_preview=True test and one for the allow_preview-unset test. - """ - yield - present_report = TestFoundryFeaturesHeaderOptionalAsync._report - if present_report: - max_len = TestFoundryFeaturesHeaderOptionalAsync._report_max_label_len - print( - "\n\nFoundry-Features optional header report (async) — test_optional_header_present_when_preview_enabled_async:" - ) - for label, header_value in sorted(present_report): - print(f'{label:<{max_len}} | "{header_value}"') - - absent_report = TestFoundryFeaturesHeaderOptionalAsync._report_absent - if absent_report: - max_len = TestFoundryFeaturesHeaderOptionalAsync._report_absent_max_label_len - print( - "\n\nFoundry-Features optional header report (async) — test_optional_header_absent_when_preview_not_enabled_async:" - ) - for label, header_value in sorted(absent_report): - print(f'{label:<{max_len}} | "{header_value}"') - - -class TestFoundryFeaturesHeaderOptionalAsync(FoundryFeaturesHeaderTestBase): - """Async tests for optional Foundry-Features header behavior on non-beta methods.""" - - _report: ClassVar[List[Tuple[str, str]]] = [] - _report_max_label_len: ClassVar[int] = 0 - _report_absent: ClassVar[List[Tuple[str, str]]] = [] - _report_absent_max_label_len: ClassVar[int] = 0 - - @staticmethod - async def _capture_async(call: Any) -> Any: - """Invoke *call()* and return the captured HttpRequest.""" - result = call() - - if inspect.isawaitable(result): - try: - await result - except _RequestCaptured as exc: - return exc.request - raise AssertionError("Transport was never called (awaitable completed without raising)") - - ai = result.__aiter__() - try: - await ai.__anext__() - except _RequestCaptured as exc: - return exc.request - except StopAsyncIteration: - raise AssertionError("Iterator exhausted without the transport being called") from None - - raise AssertionError("Transport was never called") - - @classmethod - async def _assert_header_present_async(cls, label: str, call: Any, expected_value: str) -> None: - request = await cls._capture_async(call) - cls._record_header_assertion(label, request, expected_value) - - @classmethod - async def _assert_header_absent_async(cls, label: str, call: Any) -> None: - request = await cls._capture_async(call) - cls._record_header_absence_assertion(label, request) - - @pytest.mark.asyncio - @pytest.mark.parametrize("method_name,expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) - async def test_optional_header_present_when_preview_enabled_async( - self, - async_client_preview_enabled: AsyncAIProjectClient, - method_name: str, - expected_header_value: str, - ) -> None: - subclient_name, method_attr = method_name.split(".") - sc = getattr(async_client_preview_enabled, subclient_name) - method = getattr(sc, method_attr) - await self._assert_header_present_async(method_name, self._make_fake_call(method), expected_header_value) - - @pytest.mark.asyncio - @pytest.mark.parametrize("method_name,_expected_header_value", _NON_BETA_OPTIONAL_TEST_CASES) - async def test_optional_header_absent_when_preview_not_enabled_async( - self, - async_client_preview_disabled: AsyncAIProjectClient, - method_name: str, - _expected_header_value: str, - ) -> None: - subclient_name, method_attr = method_name.split(".") - sc = getattr(async_client_preview_disabled, subclient_name) - method = getattr(sc, method_attr) - await self._assert_header_absent_async(method_name, self._make_fake_call(method)) From 89c796b016104441b0dfedbaec4d22b706e9f9b9 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:19:38 -0700 Subject: [PATCH 29/36] Another minor round of updates, after using new Python emitter (dev build of package @azure-tools/typespec-client-generator-core) (#45887) --- .../azure-ai-projects/apiview-properties.json | 2 +- .../_patch_evaluation_rules_async.py | 6 ++-- .../aio/operations/_patch_evaluators_async.py | 4 +-- .../azure/ai/projects/models/_enums.py | 4 +-- .../azure/ai/projects/models/_models.py | 30 +++++++++++-------- .../azure/ai/projects/models/_patch.py | 16 +++++----- .../ai/projects/operations/_patch_agents.py | 8 ++--- .../operations/_patch_evaluation_rules.py | 6 ++-- .../projects/operations/_patch_evaluators.py | 4 +-- .../azure-ai-projects/post-emitter-fixes.cmd | 29 ++++++++++-------- sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 11 files changed, 60 insertions(+), 51 deletions(-) diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json index cc052dec8166..f01bf53bdd60 100644 --- a/sdk/ai/azure-ai-projects/apiview-properties.json +++ b/sdk/ai/azure-ai-projects/apiview-properties.json @@ -323,4 +323,4 @@ "azure.ai.projects.operations.IndexesOperations.create_or_update": "Azure.AI.Projects.Indexes.createOrUpdateVersion", "azure.ai.projects.aio.operations.IndexesOperations.create_or_update": "Azure.AI.Projects.Indexes.createOrUpdateVersion" } -} \ No newline at end of file +} diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py index a296493c469b..717aaada342b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py @@ -14,7 +14,7 @@ from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON from ... import models as _models from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE -from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._enums import FoundryFeaturesOptInKeys from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive @@ -105,10 +105,10 @@ async def create_or_update( headers = kwargs.get("headers") if headers is None: kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + _FOUNDRY_FEATURES_HEADER_NAME: FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value } elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + headers[_FOUNDRY_FEATURES_HEADER_NAME] = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value kwargs["headers"] = headers try: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py index 1f043ffc8bef..0258bc865102 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -17,7 +17,7 @@ from azure.core.tracing.decorator_async import distributed_trace_async from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._enums import FoundryFeaturesOptInKeys from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME from ...models._models import ( CodeBasedEvaluatorDefinition, @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) -_EVALUATORS_FOUNDRY_FEATURES_VALUE = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value +_EVALUATORS_FOUNDRY_FEATURES_VALUE = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index fff2adb637c4..36d47bf29221 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -10,7 +10,7 @@ from azure.core import CaseInsensitiveEnumMeta -class _AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): +class AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Feature opt-in keys for agent definition operations supporting hosted or workflow agents.""" HOSTED_AGENTS_V1_PREVIEW = "HostedAgents=V1Preview" @@ -358,7 +358,7 @@ class EvaluatorType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Custom evaluator.""" -class _FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): +class FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Type of FoundryFeaturesOptInKeys.""" EVALUATIONS_V1_PREVIEW = "Evaluations=V1Preview" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index f009264826d7..aa45b97ca658 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -2792,7 +2792,7 @@ class ContainerNetworkPolicyAllowlistParam(ContainerNetworkPolicyParam, discrimi allowed_domains: list[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of allowed domains when type is ``allowlist``. Required.""" domain_secrets: Optional[list["_models.ContainerNetworkPolicyDomainSecretParam"]] = rest_field( - visibility=["read", "create", "update", "delete", "query"] + visibility=["create"] ) """Optional domain-scoped secrets for allowlisted domains.""" @@ -8635,13 +8635,15 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): or a Literal["required"] type. :vartype mode: str or str :ivar tools: A list of tool definitions that the model should be allowed to call. For the - Responses API, the list of tool definitions might look like: + Responses API, the list of tool definitions might look like the following. Required. + .. code-block:: json - [ - { "type": "function", "name": "get_weather" }, - { "type": "mcp", "server_label": "deepwiki" }, - { "type": "image_generation" } - ]. Required. + + [ + { "type": "function", "name": "get_weather" }, + { "type": "mcp", "server_label": "deepwiki" }, + { "type": "image_generation" } + ] :vartype tools: list[dict[str, any]] """ @@ -8654,13 +8656,15 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): Literal[\"required\"] type.""" tools: list[dict[str, Any]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of tool definitions that the model should be allowed to call. For the Responses API, the - list of tool definitions might look like: + list of tool definitions might look like the following. Required. + .. code-block:: json - [ - { \"type\": \"function\", \"name\": \"get_weather\" }, - { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, - { \"type\": \"image_generation\" } - ]. Required.""" + + [ + { \"type\": \"function\", \"name\": \"get_weather\" }, + { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, + { \"type\": \"image_generation\" } + ]""" @overload def __init__( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index 474b8b96fb86..5192c5315597 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -18,19 +18,19 @@ from azure.core.polling.async_base_polling import AsyncLROBasePolling from ._models import CustomCredential as CustomCredentialGenerated from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult -from ._enums import _FoundryFeaturesOptInKeys +from ._enums import FoundryFeaturesOptInKeys _FOUNDRY_FEATURES_HEADER_NAME: str = "Foundry-Features" """The HTTP header name used to opt in to Foundry preview features.""" _BETA_OPERATION_FEATURE_HEADERS: dict = { - "evaluation_taxonomies": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "evaluators": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "insights": _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, - "memory_stores": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, - "red_teams": _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, - "schedules": _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, - "toolsets": _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, + "evaluation_taxonomies": FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "evaluators": FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "insights": FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, + "memory_stores": FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, + "red_teams": FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, + "schedules": FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, + "toolsets": FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, } """Foundry-Features header values keyed by beta sub-client property name.""" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py index 858cbdc359df..6fa51d2097ea 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py @@ -13,7 +13,7 @@ from azure.core.tracing.decorator import distributed_trace from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset from .. import models as _models -from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys +from ..models._enums import AgentDefinitionOptInKeys, FoundryFeaturesOptInKeys from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive """ @@ -39,9 +39,9 @@ ) _AGENT_OPERATION_FEATURE_HEADERS: str = ",".join( [ - _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, - _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, - _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, + AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, + AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, + FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, ] ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py index 19dcee1ed6bd..feac2a41b5e2 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py @@ -14,7 +14,7 @@ from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE from .. import models as _models -from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._enums import FoundryFeaturesOptInKeys from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive @@ -106,10 +106,10 @@ def create_or_update( headers = kwargs.get("headers") if headers is None: kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + _FOUNDRY_FEATURES_HEADER_NAME: FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value } elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + headers[_FOUNDRY_FEATURES_HEADER_NAME] = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value kwargs["headers"] = headers try: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py index ca1edda790b3..b8ab62a98fe5 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -17,7 +17,7 @@ from azure.core.tracing.decorator import distributed_trace from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._enums import FoundryFeaturesOptInKeys from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME from ..models._models import ( CodeBasedEvaluatorDefinition, @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) -_EVALUATORS_FOUNDRY_FEATURES_VALUE = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value +_EVALUATORS_FOUNDRY_FEATURES_VALUE = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): diff --git a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd index 9092136930f6..1a098cec50e2 100644 --- a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd +++ b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd @@ -25,6 +25,10 @@ powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace REM Rename DEFAULT2024_11_15 to DEFAULT_2024_11_15 powershell -Command "(Get-Content azure\ai\projects\models\_enums.py) -replace 'DEFAULT2024_11_15', 'DEFAULT_2024_11_15' | Set-Content azure\ai\projects\models\_enums.py" +REM Make the classes AgentDefinitionOptInKeys and FoundryFeaturesOptInKeys internal +powershell -Command "(Get-Content azure\ai\projects\models\__init__.py) | Where-Object { $_ -notmatch 'AgentDefinitionOptInKeys|FoundryFeaturesOptInKeys' } | Set-Content azure\ai\projects\models\__init__.py" +powershell -Command "(Get-Content apiview-properties.json) | Where-Object { $_ -notmatch 'AgentDefinitionOptInKeys|FoundryFeaturesOptInKeys' } | Set-Content apiview-properties.json" + REM Edit both _operations.py files to fix missing Foundry-Features HTTP request header in continued list paging calls. Add: REM headers=_headers REM to the end of each of these lines in the BetaXxxOperations classes (do not do this in GA operations classes!) @@ -33,19 +37,20 @@ REM In emitted code, these first 7 of those lines are associated with GA operati REM from the 8th occurrence onward. powershell -Command "$gaCount=7; $old=[char]34+'GET'+[char]34+', urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params'; $new=$old+', headers=_headers'; foreach ($f in 'azure\ai\projects\aio\operations\_operations.py','azure\ai\projects\operations\_operations.py') { $c=Get-Content $f -Raw; $parts=$c -split [regex]::Escape($old); $r=$parts[0]; for ($i=1; $i -lt $parts.Length; $i++) { if ($i -le $gaCount) { $r+=$old+$parts[$i] } else { $r+=$new+$parts[$i] } }; Set-Content $f $r -NoNewline }" +REM Fix Sphinx issue in class ToolChoiceAllowed, in "tools" property doc string. The "Required" cannot come at the end of the code-block. +REM move it to the end of the text before the code block, and make sure there are no periods after "]". +REM .. code-block:: json +REM +REM [ +REM { "type": "function", "name": "get_weather" }, +REM { "type": "mcp", "server_label": "deepwiki" }, +REM { "type": "image_generation" } +REM ]. Required. +powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace 'Responses API, the list of tool definitions might look like:', 'Responses API, the list of tool definitions might look like the following. Required.' | Set-Content azure\ai\projects\models\_models.py" +powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace 'list of tool definitions might look like:', 'list of tool definitions might look like the following. Required.' | Set-Content azure\ai\projects\models\_models.py" +powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace ' \]\. Required\.', ' ]' | Set-Content azure\ai\projects\models\_models.py" + REM Finishing by running 'black' tool to format code. black --config ../../../eng/black-pyproject.toml . -REM No you have some more manual things to do.. - -REM Fix Sphinx issue in class ToolChoiceAllowed, in "tools" property doc string. Everything should be aligned including JSON example, like this: -REM """A list of tool definitions that the model should be allowed to call. For the Responses API, the -REM list of tool definitions might look like: -REM .. code-block:: json -REM [ -REM { \"type\": \"function\", \"name\": \"get_weather\" }, -REM { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, -REM { \"type\": \"image_generation\" } -REM ]. Required.""" - diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index 52212df81322..b28445f4cbdd 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: b09cb5a69be1c014d9f67f463d6ede22035b1088 +commit: 1a1079952c676f2e1141cfc8365b3a7816e6bc10 repo: Azure/azure-rest-api-specs additionalDirectories: From ba32dd531a03f8fe71abe7fb9f75eae6562160f9 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:44:36 -0700 Subject: [PATCH 30/36] Re-emit from latest TypeSpec in branch feature/foundry-staging (#45924) --- sdk/ai/azure-ai-projects/apiview-properties.json | 2 +- .../azure-ai-projects/azure/ai/projects/_client.py | 5 ++--- .../azure/ai/projects/_configuration.py | 7 +++---- .../azure-ai-projects/azure/ai/projects/_patch.py | 13 ++++--------- .../azure/ai/projects/aio/_client.py | 5 ++--- .../azure/ai/projects/aio/_configuration.py | 7 +++---- .../azure/ai/projects/aio/_patch.py | 5 ++--- .../azure/ai/projects/models/_enums.py | 4 ++-- .../azure/ai/projects/models/_models.py | 2 +- sdk/ai/azure-ai-projects/post-emitter-fixes.cmd | 4 ---- sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 11 files changed, 21 insertions(+), 35 deletions(-) diff --git a/sdk/ai/azure-ai-projects/apiview-properties.json b/sdk/ai/azure-ai-projects/apiview-properties.json index f01bf53bdd60..cc052dec8166 100644 --- a/sdk/ai/azure-ai-projects/apiview-properties.json +++ b/sdk/ai/azure-ai-projects/apiview-properties.json @@ -323,4 +323,4 @@ "azure.ai.projects.operations.IndexesOperations.create_or_update": "Azure.AI.Projects.Indexes.createOrUpdateVersion", "azure.ai.projects.aio.operations.IndexesOperations.create_or_update": "Azure.AI.Projects.Indexes.createOrUpdateVersion" } -} +} \ No newline at end of file diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py index 635a13982e7b..cc843f117159 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_client.py @@ -61,9 +61,8 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1" and - None. Default value is "v1". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py index 25c0f861fa8b..48a0acfed8a8 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py @@ -36,9 +36,8 @@ class AIProjectClientConfiguration: # pylint: disable=too-many-instance-attribu :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1" and - None. Default value is "v1". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -67,7 +66,7 @@ def __init__( def _infer_policy(self, **kwargs): if isinstance(self.credential, AzureKeyCredential): - return policies.AzureKeyCredentialPolicy(self.credential, "api-key", **kwargs) + return policies.AzureKeyCredentialPolicy(self.credential, "Authorization", prefix="Bearer", **kwargs) if hasattr(self.credential, "get_token"): return policies.BearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) raise TypeError(f"Unsupported credential: {self.credential}") 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 51db937048cf..8e0f246afd16 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -60,9 +60,8 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` with the appropriate value in all relevant calls to the service. :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1" and - None. Default value is "v1". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -201,14 +200,10 @@ class _AuthSecretsFilter(logging.Filter): r"(?i)(['\"]authorization['\"]\ *:\ *['\"])bearer\s+[^'\"]+(['\"])", ) - _API_KEY_HEADER_DICT_PATTERN = re.compile( - r"(?i)(['\"]api-key['\"]\ *:\ *['\"])[^'\"]+(['\"])", - ) - def filter(self, record: logging.LogRecord) -> bool: rendered = record.getMessage() - redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) - redacted = self._API_KEY_HEADER_DICT_PATTERN.sub(r"\1\2", redacted) + #redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) + redacted = rendered if redacted != rendered: # Replace the pre-formatted content so handlers emit sanitized output. record.msg = redacted diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py index 3fadfc6efc2f..3901d5155d31 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_client.py @@ -61,9 +61,8 @@ class AIProjectClient: # pylint: disable=too-many-instance-attributes :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1" and - None. Default value is "v1". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py index bd8d890b071b..045080b91b16 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py @@ -36,9 +36,8 @@ class AIProjectClientConfiguration: # pylint: disable=too-many-instance-attribu :param allow_preview: Whether to enable preview features. Must be specified and set to True to enable preview features. Default value is None. :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1" and - None. Default value is "v1". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ @@ -67,7 +66,7 @@ def __init__( def _infer_policy(self, **kwargs): if isinstance(self.credential, AzureKeyCredential): - return policies.AzureKeyCredentialPolicy(self.credential, "api-key", **kwargs) + return policies.AzureKeyCredentialPolicy(self.credential, "Authorization", prefix="Bearer", **kwargs) if hasattr(self.credential, "get_token"): return policies.AsyncBearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) raise TypeError(f"Unsupported credential: {self.credential}") 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 b5bce67b4d85..b79f5c6a93e8 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 @@ -60,9 +60,8 @@ class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-ins When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` with the appropriate value in all relevant calls to the service. :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1" and - None. Default value is "v1". Note that overriding this default value may result in unsupported - behavior. + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. :paramtype api_version: str """ diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index 36d47bf29221..6138c1a3a964 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -11,7 +11,7 @@ class AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Feature opt-in keys for agent definition operations supporting hosted or workflow agents.""" + """Opt-in keys for defining preview Hosted or Workflow Agents.""" HOSTED_AGENTS_V1_PREVIEW = "HostedAgents=V1Preview" """HOSTED_AGENTS_V1_PREVIEW.""" @@ -359,7 +359,7 @@ class EvaluatorType(str, Enum, metaclass=CaseInsensitiveEnumMeta): class FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Type of FoundryFeaturesOptInKeys.""" + """Opt-in keys for enabling preview Foundry features.""" EVALUATIONS_V1_PREVIEW = "Evaluations=V1Preview" """EVALUATIONS_V1_PREVIEW.""" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index aa45b97ca658..c9a06e9dacfa 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -2792,7 +2792,7 @@ class ContainerNetworkPolicyAllowlistParam(ContainerNetworkPolicyParam, discrimi allowed_domains: list[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of allowed domains when type is ``allowlist``. Required.""" domain_secrets: Optional[list["_models.ContainerNetworkPolicyDomainSecretParam"]] = rest_field( - visibility=["create"] + visibility=["read", "create", "update", "delete", "query"] ) """Optional domain-scoped secrets for allowlisted domains.""" diff --git a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd index 1a098cec50e2..d40eeb12c159 100644 --- a/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd +++ b/sdk/ai/azure-ai-projects/post-emitter-fixes.cmd @@ -25,10 +25,6 @@ powershell -Command "(Get-Content azure\ai\projects\models\_models.py) -replace REM Rename DEFAULT2024_11_15 to DEFAULT_2024_11_15 powershell -Command "(Get-Content azure\ai\projects\models\_enums.py) -replace 'DEFAULT2024_11_15', 'DEFAULT_2024_11_15' | Set-Content azure\ai\projects\models\_enums.py" -REM Make the classes AgentDefinitionOptInKeys and FoundryFeaturesOptInKeys internal -powershell -Command "(Get-Content azure\ai\projects\models\__init__.py) | Where-Object { $_ -notmatch 'AgentDefinitionOptInKeys|FoundryFeaturesOptInKeys' } | Set-Content azure\ai\projects\models\__init__.py" -powershell -Command "(Get-Content apiview-properties.json) | Where-Object { $_ -notmatch 'AgentDefinitionOptInKeys|FoundryFeaturesOptInKeys' } | Set-Content apiview-properties.json" - REM Edit both _operations.py files to fix missing Foundry-Features HTTP request header in continued list paging calls. Add: REM headers=_headers REM to the end of each of these lines in the BetaXxxOperations classes (do not do this in GA operations classes!) diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index b28445f4cbdd..5bcd65f47476 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: 1a1079952c676f2e1141cfc8365b3a7816e6bc10 +commit: 76b201bd18cda0e6718b176f1d06e266beb3702d repo: Azure/azure-rest-api-specs additionalDirectories: From 002b4f17c7c6960f974c96526bc133126c93e33b Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:58:33 -0700 Subject: [PATCH 31/36] Revert some api-key related changes that were not supposed to be committed --- .../azure-ai-projects/azure/ai/projects/_configuration.py | 2 +- sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py | 8 ++++++-- .../azure/ai/projects/aio/_configuration.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py index 48a0acfed8a8..ee1311403511 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_configuration.py @@ -66,7 +66,7 @@ def __init__( def _infer_policy(self, **kwargs): if isinstance(self.credential, AzureKeyCredential): - return policies.AzureKeyCredentialPolicy(self.credential, "Authorization", prefix="Bearer", **kwargs) + return policies.AzureKeyCredentialPolicy(self.credential, "api-key", **kwargs) if hasattr(self.credential, "get_token"): return policies.BearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) raise TypeError(f"Unsupported credential: {self.credential}") 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 8e0f246afd16..0b3aca9db700 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -200,10 +200,14 @@ class _AuthSecretsFilter(logging.Filter): r"(?i)(['\"]authorization['\"]\ *:\ *['\"])bearer\s+[^'\"]+(['\"])", ) + _API_KEY_HEADER_DICT_PATTERN = re.compile( + r"(?i)(['\"]api-key['\"]\ *:\ *['\"])[^'\"]+(['\"])", + ) + def filter(self, record: logging.LogRecord) -> bool: rendered = record.getMessage() - #redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) - redacted = rendered + redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) + redacted = self._API_KEY_HEADER_DICT_PATTERN.sub(r"\1\2", redacted) if redacted != rendered: # Replace the pre-formatted content so handlers emit sanitized output. record.msg = redacted diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py index 045080b91b16..4feba90abc7b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_configuration.py @@ -66,7 +66,7 @@ def __init__( def _infer_policy(self, **kwargs): if isinstance(self.credential, AzureKeyCredential): - return policies.AzureKeyCredentialPolicy(self.credential, "Authorization", prefix="Bearer", **kwargs) + return policies.AzureKeyCredentialPolicy(self.credential, "api-key", **kwargs) if hasattr(self.credential, "get_token"): return policies.AsyncBearerTokenCredentialPolicy(self.credential, *self.credential_scopes, **kwargs) raise TypeError(f"Unsupported credential: {self.credential}") From 25a0a7a744f7119f369758af3340513184a3fa32 Mon Sep 17 00:00:00 2001 From: M-Hietala <78813398+M-Hietala@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:53:58 -0500 Subject: [PATCH 32/36] disabling test that fail with pydantic beta version (#45912) --- .../telemetry/test_responses_instrumentor.py | 8 +++++ .../test_responses_instrumentor_async.py | 34 ++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py index 6f15e3e0ad74..1133311f2293 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor.py @@ -4693,6 +4693,10 @@ def test_responses_stream_method_without_content_recording(self, **kwargs): attributes_match = GenAiTraceVerifier().check_span_attributes(span, expected_attributes) assert attributes_match == True + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.HTTPX) @@ -4800,6 +4804,10 @@ def test_responses_stream_method_with_tools_with_content_recording(self, **kwarg # Validate second span (tool output + final response) _span2 = spans[1] # pylint: disable=unused-variable + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy(RecordedTransport.HTTPX) diff --git a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py index a0c7c0f5ad6c..b6fbf6c76a3d 100644 --- a/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py +++ b/sdk/ai/azure-ai-projects/tests/agents/telemetry/test_responses_instrumentor_async.py @@ -1,4 +1,4 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression +# pylint: disable=too-many-lines,line-too-long,useless-suppression # ------------------------------------ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. @@ -2922,6 +2922,10 @@ async def _test_async_responses_stream_method_with_tools_with_content_recording_ events_match = GenAiTraceVerifier().check_span_events(span2, expected_events_2) assert events_match == True + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -2929,6 +2933,10 @@ async def test_async_responses_stream_method_with_tools_with_content_recording_e """Test async responses.stream() with tools and content recording (event-based messages).""" await self._test_async_responses_stream_method_with_tools_with_content_recording_impl(True, **kwargs) + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -2936,6 +2944,10 @@ async def test_async_responses_stream_method_with_tools_with_content_recording_a """Test async responses.stream() with tools and content recording (attribute-based messages).""" await self._test_async_responses_stream_method_with_tools_with_content_recording_impl(False, **kwargs) + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -2945,6 +2957,10 @@ async def test_async_responses_stream_method_with_tools_with_content_recording_s True, use_simple_tool_call_format=True, **kwargs ) + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_with_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -3155,6 +3171,10 @@ async def _test_async_responses_stream_method_with_tools_without_content_recordi events_match = GenAiTraceVerifier().check_span_events(span2, expected_events_2) assert events_match == True + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -3162,6 +3182,10 @@ async def test_async_responses_stream_method_with_tools_without_content_recordin """Test async responses.stream() with tools, without content recording (event-based messages).""" await self._test_async_responses_stream_method_with_tools_without_content_recording_impl(True, **kwargs) + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -3169,6 +3193,10 @@ async def test_async_responses_stream_method_with_tools_without_content_recordin """Test async responses.stream() with tools, without content recording (attribute-based messages).""" await self._test_async_responses_stream_method_with_tools_without_content_recording_impl(False, **kwargs) + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) @@ -3180,6 +3208,10 @@ async def test_async_responses_stream_method_with_tools_without_content_recordin True, use_simple_tool_call_format=True, **kwargs ) + @pytest.mark.skip( + reason="Fails with pydantic>=2.13.0b2 / pydantic-core>=2.42.0: MockValSer is not accepted as SchemaSerializer, " + "causing TypeError when iterating responses.stream() that includes function tools (not related to instrumentation)" + ) @pytest.mark.usefixtures("instrument_without_content") @servicePreparer() @recorded_by_proxy_async(RecordedTransport.HTTPX) From 1e49fd57a1112c9a3d1bc88692bdb0f8a4b884d6 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Thu, 26 Mar 2026 11:36:22 -0700 Subject: [PATCH 33/36] Add underscore to FoundryFeaturesOptInKeys and AgentDefinitionOptInKeys names as they are now package-internal (#45938) --- .../_patch_evaluation_rules_async.py | 6 +-- .../aio/operations/_patch_evaluators_async.py | 6 +-- .../azure/ai/projects/models/_enums.py | 40 +++++++++---------- .../azure/ai/projects/models/_patch.py | 26 ++++++------ .../ai/projects/operations/_patch_agents.py | 10 ++--- .../operations/_patch_evaluation_rules.py | 6 +-- .../projects/operations/_patch_evaluators.py | 6 +-- sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 8 files changed, 51 insertions(+), 51 deletions(-) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py index 717aaada342b..a296493c469b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py @@ -14,7 +14,7 @@ from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON from ... import models as _models from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE -from ...models._enums import FoundryFeaturesOptInKeys +from ...models._enums import _FoundryFeaturesOptInKeys from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive @@ -105,10 +105,10 @@ async def create_or_update( headers = kwargs.get("headers") if headers is None: kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value } elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value kwargs["headers"] = headers try: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py index 0258bc865102..c6c366fd5956 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -10,14 +10,14 @@ import os import logging -from typing import Any, IO, Tuple, Optional, Union +from typing import Any, Final, IO, Tuple, Optional, Union from pathlib import Path from urllib.parse import urlsplit from azure.storage.blob.aio import ContainerClient from azure.core.tracing.decorator_async import distributed_trace_async from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ...models._enums import FoundryFeaturesOptInKeys +from ...models._enums import _FoundryFeaturesOptInKeys from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME from ...models._models import ( CodeBasedEvaluatorDefinition, @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) -_EVALUATORS_FOUNDRY_FEATURES_VALUE = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value +_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index 6138c1a3a964..2c528dea5d0d 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -10,7 +10,7 @@ from azure.core import CaseInsensitiveEnumMeta -class AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): +class _AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Opt-in keys for defining preview Hosted or Workflow Agents.""" HOSTED_AGENTS_V1_PREVIEW = "HostedAgents=V1Preview" @@ -19,6 +19,25 @@ class AgentDefinitionOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): """WORKFLOW_AGENTS_V1_PREVIEW.""" +class _FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Opt-in keys for enabling preview Foundry features.""" + + EVALUATIONS_V1_PREVIEW = "Evaluations=V1Preview" + """EVALUATIONS_V1_PREVIEW.""" + SCHEDULES_V1_PREVIEW = "Schedules=V1Preview" + """SCHEDULES_V1_PREVIEW.""" + RED_TEAMS_V1_PREVIEW = "RedTeams=V1Preview" + """RED_TEAMS_V1_PREVIEW.""" + INSIGHTS_V1_PREVIEW = "Insights=V1Preview" + """INSIGHTS_V1_PREVIEW.""" + MEMORY_STORES_V1_PREVIEW = "MemoryStores=V1Preview" + """MEMORY_STORES_V1_PREVIEW.""" + TOOLSET_V1_PREVIEW = "Toolsets=V1Preview" + """TOOLSET_V1_PREVIEW.""" + AGENT_ENDPOINT_V1_PREVIEW = "AgentEndpoints=V1Preview" + """AGENT_ENDPOINT_V1_PREVIEW.""" + + class AgentKind(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Type of AgentKind.""" @@ -358,25 +377,6 @@ class EvaluatorType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Custom evaluator.""" -class FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Opt-in keys for enabling preview Foundry features.""" - - EVALUATIONS_V1_PREVIEW = "Evaluations=V1Preview" - """EVALUATIONS_V1_PREVIEW.""" - SCHEDULES_V1_PREVIEW = "Schedules=V1Preview" - """SCHEDULES_V1_PREVIEW.""" - RED_TEAMS_V1_PREVIEW = "RedTeams=V1Preview" - """RED_TEAMS_V1_PREVIEW.""" - INSIGHTS_V1_PREVIEW = "Insights=V1Preview" - """INSIGHTS_V1_PREVIEW.""" - MEMORY_STORES_V1_PREVIEW = "MemoryStores=V1Preview" - """MEMORY_STORES_V1_PREVIEW.""" - TOOLSET_V1_PREVIEW = "Toolsets=V1Preview" - """TOOLSET_V1_PREVIEW.""" - AGENT_ENDPOINT_V1_PREVIEW = "AgentEndpoints=V1Preview" - """AGENT_ENDPOINT_V1_PREVIEW.""" - - class FunctionShellToolParamEnvironmentType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Type of FunctionShellToolParamEnvironmentType.""" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index 5192c5315597..658e4c90fb2f 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -8,7 +8,7 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import List, Dict, Mapping, Optional, Any, Tuple +from typing import Final, FrozenSet, List, Dict, Mapping, Optional, Any, Tuple from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod from azure.core.polling.base_polling import ( LROBasePolling, @@ -18,19 +18,19 @@ from azure.core.polling.async_base_polling import AsyncLROBasePolling from ._models import CustomCredential as CustomCredentialGenerated from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult -from ._enums import FoundryFeaturesOptInKeys +from ._enums import _FoundryFeaturesOptInKeys -_FOUNDRY_FEATURES_HEADER_NAME: str = "Foundry-Features" +_FOUNDRY_FEATURES_HEADER_NAME: Final[str] = "Foundry-Features" """The HTTP header name used to opt in to Foundry preview features.""" -_BETA_OPERATION_FEATURE_HEADERS: dict = { - "evaluation_taxonomies": FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "evaluators": FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "insights": FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, - "memory_stores": FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, - "red_teams": FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, - "schedules": FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, - "toolsets": FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, +_BETA_OPERATION_FEATURE_HEADERS: Final[dict] = { + "evaluation_taxonomies": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "evaluators": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "insights": _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, + "memory_stores": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, + "red_teams": _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, + "schedules": _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, + "toolsets": _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, } """Foundry-Features header values keyed by beta sub-client property name.""" @@ -96,8 +96,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.credential_keys = {} -_FINISHED = frozenset(["completed", "superseded", "failed"]) -_FAILED = frozenset(["failed"]) +_FINISHED: Final[FrozenSet[str]] = frozenset(["completed", "superseded", "failed"]) +_FAILED: Final[FrozenSet[str]] = frozenset(["failed"]) class _UpdateMemoriesLROPollingMethod(LROBasePolling): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py index 6fa51d2097ea..8c19e6279729 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py @@ -13,7 +13,7 @@ from azure.core.tracing.decorator import distributed_trace from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset from .. import models as _models -from ..models._enums import AgentDefinitionOptInKeys, FoundryFeaturesOptInKeys +from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive """ @@ -37,11 +37,11 @@ "when calling the AIProjectClient constructor. " "\nNote that preview features are under development and subject to change." ) -_AGENT_OPERATION_FEATURE_HEADERS: str = ",".join( +_AGENT_OPERATION_FEATURE_HEADERS: Final[str] = ",".join( [ - AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, - AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, - FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, + _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, + _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, + _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, ] ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py index feac2a41b5e2..19dcee1ed6bd 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py @@ -14,7 +14,7 @@ from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE from .. import models as _models -from ..models._enums import FoundryFeaturesOptInKeys +from ..models._enums import _FoundryFeaturesOptInKeys from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive @@ -106,10 +106,10 @@ def create_or_update( headers = kwargs.get("headers") if headers is None: kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value } elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value kwargs["headers"] = headers try: diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py index b8ab62a98fe5..3f1c38d97b2b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -10,14 +10,14 @@ import os import logging -from typing import Any, IO, Tuple, Optional, Union +from typing import Any, Final, IO, Tuple, Optional, Union from pathlib import Path from urllib.parse import urlsplit from azure.storage.blob import ContainerClient from azure.core.tracing.decorator import distributed_trace from azure.core.exceptions import HttpResponseError, ResourceNotFoundError from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ..models._enums import FoundryFeaturesOptInKeys +from ..models._enums import _FoundryFeaturesOptInKeys from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME from ..models._models import ( CodeBasedEvaluatorDefinition, @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) -_EVALUATORS_FOUNDRY_FEATURES_VALUE = FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value +_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index 5bcd65f47476..01ecabb49ebf 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: 76b201bd18cda0e6718b176f1d06e266beb3702d +commit: e50321286a093e4cd6c77edfa7162d73d142f594 repo: Azure/azure-rest-api-specs additionalDirectories: From 39fb2282e338f58e262ce21aac00fc17f1624642 Mon Sep 17 00:00:00 2001 From: azure-sdk Date: Thu, 26 Mar 2026 18:50:33 +0000 Subject: [PATCH 34/36] Configurations: 'specification/ai-foundry/data-plane/Foundry/tspconfig.yaml', API Version: v1, SDK Release Type: stable, and CommitSHA: '57e7a9ebf3c779e4f8c90b9e140bf09c14aa0830' in SpecRepo: 'https://github.com/Azure/azure-rest-api-specs' Pipeline run: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=6069658 Refer to https://eng.ms/docs/products/azure-developer-experience/develop/sdk-release/sdk-release-prerequisites to prepare for SDK release. --- sdk/ai/azure-ai-projects/CHANGELOG.md | 4 + sdk/ai/azure-ai-projects/_metadata.json | 6 +- .../azure/ai/projects/_patch.py | 321 +- .../azure/ai/projects/_validation.py | 66 - .../azure/ai/projects/_version.py | 2 +- .../azure/ai/projects/aio/_patch.py | 297 +- .../ai/projects/aio/operations/_operations.py | 39 +- .../ai/projects/aio/operations/_patch.py | 84 +- .../aio/operations/_patch_agents_async.py | 192 - .../operations/_patch_connections_async.py | 74 - .../aio/operations/_patch_datasets_async.py | 223 - .../_patch_evaluation_rules_async.py | 129 - .../aio/operations/_patch_evaluators_async.py | 258 - .../aio/operations/_patch_memories_async.py | 379 -- .../aio/operations/_patch_telemetry_async.py | 75 - .../azure/ai/projects/models/_enums.py | 8 +- .../azure/ai/projects/models/_models.py | 18 +- .../azure/ai/projects/models/_patch.py | 352 +- .../ai/projects/operations/_operations.py | 39 +- .../azure/ai/projects/operations/_patch.py | 139 +- .../ai/projects/operations/_patch_agents.py | 218 - .../projects/operations/_patch_connections.py | 63 - .../ai/projects/operations/_patch_datasets.py | 222 - .../operations/_patch_evaluation_rules.py | 130 - .../projects/operations/_patch_evaluators.py | 258 - .../ai/projects/operations/_patch_memories.py | 414 -- .../projects/operations/_patch_telemetry.py | 69 - .../azure/ai/projects/telemetry/__init__.py | 12 - .../telemetry/_ai_project_instrumentor.py | 1535 ------ .../telemetry/_responses_instrumentor.py | 4883 ----------------- .../ai/projects/telemetry/_trace_function.py | 206 - .../azure/ai/projects/telemetry/_utils.py | 278 - sdk/ai/azure-ai-projects/pyproject.toml | 18 +- sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 34 files changed, 80 insertions(+), 10933 deletions(-) delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index b254a155fd3d..a6bbf0926b62 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## 2.1.0 (2026-03-26) + +skip changelog generation for data-plane package and please add changelog manually. + ## 2.0.2 (Unreleased) ### Features Added diff --git a/sdk/ai/azure-ai-projects/_metadata.json b/sdk/ai/azure-ai-projects/_metadata.json index 3a000fe50d57..74dccbab5ffe 100644 --- a/sdk/ai/azure-ai-projects/_metadata.json +++ b/sdk/ai/azure-ai-projects/_metadata.json @@ -2,5 +2,9 @@ "apiVersion": "v1", "apiVersions": { "Azure.AI.Projects": "v1" - } + }, + "commit": "57e7a9ebf3c779e4f8c90b9e140bf09c14aa0830", + "repository_url": "https://github.com/Azure/azure-rest-api-specs", + "typespec_src": "specification/ai-foundry/data-plane/Foundry", + "emitterVersion": "0.61.1" } \ No newline at end of file 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 0b3aca9db700..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -1,324 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import os -import re -import logging -from typing import List, Any, Union -import httpx # pylint: disable=networking-import-outside-azure-core-transport -from openai import OpenAI -from azure.core.credentials import AzureKeyCredential -from azure.core.tracing.decorator import distributed_trace -from azure.core.credentials import TokenCredential -from azure.identity import get_bearer_token_provider -from ._client import AIProjectClient as AIProjectClientGenerated -from .operations import TelemetryOperations -logger = logging.getLogger(__name__) - - -class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-instance-attributes - """AIProjectClient. - - :ivar beta: BetaOperations operations - :vartype beta: azure.ai.projects.operations.BetaOperations - :ivar agents: AgentsOperations operations - :vartype agents: azure.ai.projects.operations.AgentsOperations - :ivar evaluation_rules: EvaluationRulesOperations operations - :vartype evaluation_rules: azure.ai.projects.operations.EvaluationRulesOperations - :ivar connections: ConnectionsOperations operations - :vartype connections: azure.ai.projects.operations.ConnectionsOperations - :ivar datasets: DatasetsOperations operations - :vartype datasets: azure.ai.projects.operations.DatasetsOperations - :ivar deployments: DeploymentsOperations operations - :vartype deployments: azure.ai.projects.operations.DeploymentsOperations - :ivar indexes: IndexesOperations operations - :vartype indexes: azure.ai.projects.operations.IndexesOperations - :param endpoint: Foundry Project endpoint in the form - "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you - only have one Project in your Foundry Hub, or to target the default Project in your Hub, use - the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". - Required. - :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Is either a key - credential type or a token credential type. Required. - :type credential: ~azure.core.credentials.AzureKeyCredential or - ~azure.core.credentials.TokenCredential - :param allow_preview: Whether to enable preview features. Optional, default is False. - Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) - or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). - Set this to True to use human evaluation rule action (class :class:`~azure.ai.projects.models.HumanEvaluationPreviewRuleAction`). - Methods on the `.beta` sub-client (class :class:`~azure.ai.projects.operations.BetaOperations`) - are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. - When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` - with the appropriate value in all relevant calls to the service. - :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1". Default - value is "v1". Note that overriding this default value may result in unsupported behavior. - :paramtype api_version: str - """ - - def __init__( - self, - endpoint: str, - credential: Union[AzureKeyCredential, "TokenCredential"], - *, - allow_preview: bool = False, - **kwargs: Any, - ) -> None: - - self._console_logging_enabled: bool = ( - os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" - ) - - if self._console_logging_enabled: - import sys - - # Enable detailed console logs across Azure libraries - azure_logger = logging.getLogger("azure") - azure_logger.setLevel(logging.DEBUG) - console_handler = logging.StreamHandler(stream=sys.stdout) - console_handler.addFilter(_AuthSecretsFilter()) - azure_logger.addHandler(console_handler) - # Exclude detailed logs for network calls associated with getting Entra ID token. - logging.getLogger("azure.identity").setLevel(logging.ERROR) - # Make sure regular (redacted) detailed azure.core logs are not shown, as we are about to - # turn on non-redacted logs by passing 'logging_enable=True' to the client constructor - # (which are implemented as a separate logging policy) - logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.ERROR) - - kwargs.setdefault("logging_enable", self._console_logging_enabled) - - self._kwargs = kwargs.copy() - self._custom_user_agent = self._kwargs.get("user_agent", None) - - super().__init__(endpoint=endpoint, credential=credential, allow_preview=allow_preview, **kwargs) - - self.telemetry = TelemetryOperations(self) # type: ignore - - @distributed_trace - def get_openai_client(self, **kwargs: Any) -> OpenAI: - """Get an authenticated OpenAI client from the `openai` package. - - Keyword arguments are passed to the OpenAI client constructor. - - The OpenAI client constructor is called with: - * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. - Can be overridden by passing ``base_url`` as a keyword argument. - * If :class:`~azure.ai.projects.AIProjectClient` was constructed with a bearer token, ``api_key`` is set - to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient - constructor, with scope ``https://ai.azure.com/.default``. - Can be overridden by passing ``api_key`` as a keyword argument. - * If :class:`~azure.ai.projects.AIProjectClient` was constructed with ``api-key``, it is passed to the - OpenAI constructor as is. - Can be overridden by passing ``api_key`` as a keyword argument. - - .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. - - :return: An authenticated OpenAI client - :rtype: ~openai.OpenAI - - :raises ~azure.core.exceptions.HttpResponseError: - """ - - kwargs = kwargs.copy() if kwargs else {} - - # Allow caller to override base_url - if "base_url" in kwargs: - base_url = kwargs.pop("base_url") - else: - base_url = self._config.endpoint.rstrip("/") + "/openai/v1" # pylint: disable=protected-access - - 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, - ) - - # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor - if "api_key" in kwargs: - api_key = kwargs.pop("api_key") - else: - api_key = ( - self._config.credential.key # pylint: disable=protected-access - if isinstance(self._config.credential, AzureKeyCredential) - else get_bearer_token_provider( - self._config.credential, # pylint: disable=protected-access - "https://ai.azure.com/.default", - ) - ) - - if "http_client" in kwargs: - http_client = kwargs.pop("http_client") - elif self._console_logging_enabled: - http_client = httpx.Client(transport=OpenAILoggingTransport()) - else: - http_client = None - - default_headers = dict[str, str](kwargs.pop("default_headers", None) or {}) - - openai_custom_user_agent = default_headers.get("User-Agent", None) - - def _create_openai_client(**kwargs) -> OpenAI: - return OpenAI( - api_key=api_key, - base_url=base_url, - http_client=http_client, - **kwargs, - ) - - dummy_client = _create_openai_client() - - openai_default_user_agent = dummy_client.user_agent - - if openai_custom_user_agent: - final_user_agent = openai_custom_user_agent - else: - final_user_agent = ( - "-".join(ua for ua in [self._custom_user_agent, "AIProjectClient"] if ua) - + " " - + openai_default_user_agent - ) - - default_headers["User-Agent"] = final_user_agent - - client = _create_openai_client(default_headers=default_headers, **kwargs) - - return client - - -class _AuthSecretsFilter(logging.Filter): - """Redact bearer tokens and api-key values in azure.core log messages before they are emitted to console.""" - - _AUTH_HEADER_DICT_PATTERN = re.compile( - r"(?i)(['\"]authorization['\"]\ *:\ *['\"])bearer\s+[^'\"]+(['\"])", - ) - - _API_KEY_HEADER_DICT_PATTERN = re.compile( - r"(?i)(['\"]api-key['\"]\ *:\ *['\"])[^'\"]+(['\"])", - ) - - def filter(self, record: logging.LogRecord) -> bool: - rendered = record.getMessage() - redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) - redacted = self._API_KEY_HEADER_DICT_PATTERN.sub(r"\1\2", redacted) - if redacted != rendered: - # Replace the pre-formatted content so handlers emit sanitized output. - record.msg = redacted - record.args = () - return True - - -class OpenAILoggingTransport(httpx.HTTPTransport): - """Custom HTTP transport that logs OpenAI API requests and responses to the console. - - This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and print - detailed request/response information for debugging purposes. It automatically - redacts sensitive authorization headers and handles various content types including - multipart form data (file uploads). - - Used internally by AIProjectClient when console logging is enabled via the - AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable. - """ - - def _sanitize_auth_header(self, headers) -> None: - """Sanitize authorization and api-key headers by redacting sensitive information. - - :param headers: Dictionary of HTTP headers to sanitize - :type headers: dict - """ - - if "authorization" in headers: - auth_value = headers["authorization"] - if len(auth_value) >= 7: - headers["authorization"] = auth_value[:7] + "" - else: - headers["authorization"] = "" - - def handle_request(self, request: httpx.Request) -> httpx.Response: - """ - Log HTTP request and response details to console, in a nicely formatted way, - for OpenAI / Azure OpenAI clients. - - :param request: The HTTP request to handle and log - :type request: httpx.Request - - :return: The HTTP response received - :rtype: httpx.Response - """ - - print(f"\n==> Request:\n{request.method} {request.url}") - headers = dict(request.headers) - self._sanitize_auth_header(headers) - print("Headers:") - for key, value in sorted(headers.items()): - print(f" {key}: {value}") - - self._log_request_body(request) - - response = super().handle_request(request) - - print(f"\n<== Response:\n{response.status_code} {response.reason_phrase}") - print("Headers:") - for key, value in sorted(dict(response.headers).items()): - print(f" {key}: {value}") - - content = response.read() - if content is None or content == b"": - print("Body: [No content]") - else: - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - print("\n") - - return response - - def _log_request_body(self, request: httpx.Request) -> None: - """Log request body content safely, handling binary data and streaming content. - - :param request: The HTTP request object containing the body to log - :type request: httpx.Request - """ - - # Check content-type header to identify file uploads - content_type = request.headers.get("content-type", "").lower() - if "multipart/form-data" in content_type: - print("Body: [Multipart form data - file upload, not logged]") - return - - # Safely check if content exists without accessing it - if not hasattr(request, "content"): - print("Body: [No content attribute]") - return - - # Very careful content access - wrap in try-catch immediately - try: - content = request.content - except Exception as access_error: # pylint: disable=broad-exception-caught - print(f"Body: [Cannot access content: {access_error}]") - return - - if content is None or content == b"": - print("Body: [No content]") - return - - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - - -__all__: List[str] = [ - "AIProjectClient", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py deleted file mode 100644 index f5af3a4eb8a2..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py +++ /dev/null @@ -1,66 +0,0 @@ -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# Code generated by Microsoft (R) Python Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is regenerated. -# -------------------------------------------------------------------------- -import functools - - -def api_version_validation(**kwargs): - params_added_on = kwargs.pop("params_added_on", {}) - method_added_on = kwargs.pop("method_added_on", "") - api_versions_list = kwargs.pop("api_versions_list", []) - - def _index_with_default(value: str, default: int = -1) -> int: - """Get the index of value in lst, or return default if not found. - - :param value: The value to search for in the api_versions_list. - :type value: str - :param default: The default value to return if the value is not found. - :type default: int - :return: The index of the value in the list, or the default value if not found. - :rtype: int - """ - try: - return api_versions_list.index(value) - except ValueError: - return default - - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - try: - # this assumes the client has an _api_version attribute - client = args[0] - client_api_version = client._config.api_version # pylint: disable=protected-access - except AttributeError: - return func(*args, **kwargs) - - if _index_with_default(method_added_on) > _index_with_default(client_api_version): - raise ValueError( - f"'{func.__name__}' is not available in API version " - f"{client_api_version}. Pass service API version {method_added_on} or newer to your client." - ) - - unsupported = { - parameter: api_version - for api_version, parameters in params_added_on.items() - for parameter in parameters - if parameter in kwargs and _index_with_default(api_version) > _index_with_default(client_api_version) - } - if unsupported: - raise ValueError( - "".join( - [ - f"'{param}' is not available in API version {client_api_version}. " - f"Use service API version {version} or newer.\n" - for param, version in unsupported.items() - ] - ) - ) - return func(*args, **kwargs) - - return wrapper - - return decorator diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py index 85d7b3924370..ccb75164d3bc 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "2.0.2" +VERSION = "2.1.0" 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 b79f5c6a93e8..87676c65a8f0 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 @@ -1,300 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import os -import logging -from typing import List, Any, Union -import httpx # pylint: disable=networking-import-outside-azure-core-transport -from openai import AsyncOpenAI -from azure.core.credentials import AzureKeyCredential -from azure.core.tracing.decorator import distributed_trace -from azure.core.credentials_async import AsyncTokenCredential -from azure.identity.aio import get_bearer_token_provider -from .._patch import _AuthSecretsFilter -from ._client import AIProjectClient as AIProjectClientGenerated -from .operations import TelemetryOperations -logger = logging.getLogger(__name__) - - -class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-instance-attributes - """AIProjectClient. - - :ivar beta: BetaOperations operations - :vartype beta: azure.ai.projects.aio.operations.BetaOperations - :ivar agents: AgentsOperations operations - :vartype agents: azure.ai.projects.aio.operations.AgentsOperations - :ivar evaluation_rules: EvaluationRulesOperations operations - :vartype evaluation_rules: azure.ai.projects.aio.operations.EvaluationRulesOperations - :ivar connections: ConnectionsOperations operations - :vartype connections: azure.ai.projects.aio.operations.ConnectionsOperations - :ivar datasets: DatasetsOperations operations - :vartype datasets: azure.ai.projects.aio.operations.DatasetsOperations - :ivar deployments: DeploymentsOperations operations - :vartype deployments: azure.ai.projects.aio.operations.DeploymentsOperations - :ivar indexes: IndexesOperations operations - :vartype indexes: azure.ai.projects.aio.operations.IndexesOperations - :param endpoint: Foundry Project endpoint in the form - "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you - only have one Project in your Foundry Hub, or to target the default Project in your Hub, use - the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". - Required. - :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Is either a key - credential type or a token credential type. Required. - :type credential: ~azure.core.credentials.AzureKeyCredential or - ~azure.core.credentials_async.AsyncTokenCredential - :param allow_preview: Whether to enable preview features. Optional, default is False. - Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) - or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). - Set this to True to use human evaluation rule action (class :class:`~azure.ai.projects.models.HumanEvaluationPreviewRuleAction`). - Methods on the `.beta` sub-client (class :class:`~azure.ai.projects.aio.operations.BetaOperations`) - are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. - When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` - with the appropriate value in all relevant calls to the service. - :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1". Default - value is "v1". Note that overriding this default value may result in unsupported behavior. - :paramtype api_version: str - """ - - def __init__( - self, - endpoint: str, - credential: Union[AzureKeyCredential, "AsyncTokenCredential"], - *, - allow_preview: bool = False, - **kwargs: Any, - ) -> None: - - self._console_logging_enabled: bool = ( - os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" - ) - - if self._console_logging_enabled: - import sys - - # Enable detailed console logs across Azure libraries - azure_logger = logging.getLogger("azure") - azure_logger.setLevel(logging.DEBUG) - console_handler = logging.StreamHandler(stream=sys.stdout) - console_handler.addFilter(_AuthSecretsFilter()) - azure_logger.addHandler(console_handler) - # Exclude detailed logs for network calls associated with getting Entra ID token. - logging.getLogger("azure.identity").setLevel(logging.ERROR) - # Make sure regular (redacted) detailed azure.core logs are not shown, as we are about to - # turn on non-redacted logs by passing 'logging_enable=True' to the client constructor - # (which are implemented as a separate logging policy) - logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.ERROR) - - kwargs.setdefault("logging_enable", self._console_logging_enabled) - - self._kwargs = kwargs.copy() - self._custom_user_agent = self._kwargs.get("user_agent", None) - - super().__init__(endpoint=endpoint, credential=credential, allow_preview=allow_preview, **kwargs) - - self.telemetry = TelemetryOperations(self) # type: ignore - - @distributed_trace - def get_openai_client(self, **kwargs: Any) -> AsyncOpenAI: - """Get an authenticated AsyncOpenAI client from the `openai` package. - - Keyword arguments are passed to the AsyncOpenAI client constructor. - - The AsyncOpenAI client constructor is called with: - * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. - Can be overridden by passing ``base_url`` as a keyword argument. - * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with a bearer token, ``api_key`` is set - to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient - constructor, with scope ``https://ai.azure.com/.default``. - Can be overridden by passing ``api_key`` as a keyword argument. - * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with ``api-key``, it is passed to the - OpenAI constructor as is. - Can be overridden by passing ``api_key`` as a keyword argument. - - .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. - - :return: An authenticated AsyncOpenAI client - :rtype: ~openai.AsyncOpenAI - - :raises ~azure.core.exceptions.HttpResponseError: - """ - - kwargs = kwargs.copy() if kwargs else {} - - # Allow caller to override base_url - if "base_url" in kwargs: - base_url = kwargs.pop("base_url") - else: - base_url = self._config.endpoint.rstrip("/") + "/openai/v1" # pylint: disable=protected-access - - 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, - ) - - # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor - if "api_key" in kwargs: - api_key = kwargs.pop("api_key") - else: - api_key = ( - self._config.credential.key # pylint: disable=protected-access - if isinstance(self._config.credential, AzureKeyCredential) - else get_bearer_token_provider( - self._config.credential, # pylint: disable=protected-access - "https://ai.azure.com/.default", - ) - ) - - if "http_client" in kwargs: - http_client = kwargs.pop("http_client") - elif self._console_logging_enabled: - http_client = httpx.AsyncClient(transport=OpenAILoggingTransport()) - else: - http_client = None - - default_headers = dict[str, str](kwargs.pop("default_headers", None) or {}) - - openai_custom_user_agent = default_headers.get("User-Agent", None) - - def _create_openai_client(**kwargs) -> AsyncOpenAI: - return AsyncOpenAI( - api_key=api_key, - base_url=base_url, - http_client=http_client, - **kwargs, - ) - - dummy_client = _create_openai_client() - - openai_default_user_agent = dummy_client.user_agent - - if openai_custom_user_agent: - final_user_agent = openai_custom_user_agent - else: - final_user_agent = ( - "-".join(ua for ua in [self._custom_user_agent, "AIProjectClient"] if ua) - + " " - + openai_default_user_agent - ) - - default_headers["User-Agent"] = final_user_agent - - client = _create_openai_client(default_headers=default_headers, **kwargs) - - return client - - -class OpenAILoggingTransport(httpx.AsyncHTTPTransport): - """Custom HTTP async transport that logs OpenAI API requests and responses to the console. - - This transport wraps httpx.AsyncHTTPTransport to intercept all HTTP traffic and print - detailed request/response information for debugging purposes. It automatically - redacts sensitive authorization headers and handles various content types including - multipart form data (file uploads). - - Used internally by AIProjectClient when console logging is enabled via the - AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable. - """ - - def _sanitize_auth_header(self, headers): - """Sanitize authorization and api-key headers by redacting sensitive information. - - :param headers: Dictionary of HTTP headers to sanitize - :type headers: dict - """ - - if "authorization" in headers: - auth_value = headers["authorization"] - if len(auth_value) >= 7: - headers["authorization"] = auth_value[:7] + "" - else: - headers["authorization"] = "" - - async def handle_async_request(self, request: httpx.Request) -> httpx.Response: - """ - Log HTTP request and response details to console, in a nicely formatted way, - for OpenAI / Azure OpenAI clients. - - :param request: The HTTP request to handle and log - :type request: httpx.Request - - :return: The HTTP response received - :rtype: httpx.Response - """ - - print(f"\n==> Request:\n{request.method} {request.url}") - headers = dict(request.headers) - self._sanitize_auth_header(headers) - print("Headers:") - for key, value in sorted(headers.items()): - print(f" {key}: {value}") - - self._log_request_body(request) - - response = await super().handle_async_request(request) - - print(f"\n<== Response:\n{response.status_code} {response.reason_phrase}") - print("Headers:") - for key, value in sorted(dict(response.headers).items()): - print(f" {key}: {value}") - - content = await response.aread() - if content is None or content == b"": - print("Body: [No content]") - else: - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - print("\n") - - return response - - def _log_request_body(self, request: httpx.Request) -> None: - """Log request body content safely, handling binary data and streaming content. - - :param request: The HTTP request object containing the body to log - :type request: httpx.Request - """ - - # Check content-type header to identify file uploads - content_type = request.headers.get("content-type", "").lower() - if "multipart/form-data" in content_type: - print("Body: [Multipart form data - file upload, not logged]") - return - - # Safely check if content exists without accessing it - if not hasattr(request, "content"): - print("Body: [No content attribute]") - return - - # Very careful content access - wrap in try-catch immediately - try: - content = request.content - except Exception as access_error: # pylint: disable=broad-exception-caught - print(f"Body: [Cannot access content: {access_error}]") - return - - if content is None or content == b"": - print("Body: [No content]") - return - - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - - -__all__: List[str] = ["AIProjectClient"] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 50659948669c..7bffabfd89e1 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -3097,10 +3097,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3530,10 +3527,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3633,10 +3627,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -4693,10 +4684,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5364,7 +5352,7 @@ async def _search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -5450,7 +5438,7 @@ async def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, @@ -5898,10 +5886,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6247,10 +6232,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6550,10 +6532,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index c448e46abae8..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -1,87 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import Any, List -from ._patch_agents_async import AgentsOperations -from ._patch_datasets_async import DatasetsOperations -from ._patch_evaluation_rules_async import EvaluationRulesOperations -from ._patch_evaluators_async import BetaEvaluatorsOperations -from ._patch_telemetry_async import TelemetryOperations -from ._patch_connections_async import ConnectionsOperations -from ._patch_memories_async import BetaMemoryStoresOperations -from ...operations._patch import _BETA_OPERATION_FEATURE_HEADERS, _OperationMethodHeaderProxy -from ._operations import ( - BetaEvaluationTaxonomiesOperations, - BetaInsightsOperations, - BetaOperations as GeneratedBetaOperations, - BetaRedTeamsOperations, - BetaSchedulesOperations, - BetaToolsetsOperations, -) - -class BetaOperations(GeneratedBetaOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`beta` attribute. - """ - - evaluation_taxonomies: BetaEvaluationTaxonomiesOperations - """:class:`~azure.ai.projects.aio.operations.BetaEvaluationTaxonomiesOperations` operations""" - evaluators: BetaEvaluatorsOperations - """:class:`~azure.ai.projects.aio.operations.BetaEvaluatorsOperations` operations""" - insights: BetaInsightsOperations - """:class:`~azure.ai.projects.aio.operations.BetaInsightsOperations` operations""" - memory_stores: BetaMemoryStoresOperations - """:class:`~azure.ai.projects.aio.operations.BetaMemoryStoresOperations` operations""" - red_teams: BetaRedTeamsOperations - """:class:`~azure.ai.projects.aio.operations.BetaRedTeamsOperations` operations""" - schedules: BetaSchedulesOperations - """:class:`~azure.ai.projects.aio.operations.BetaSchedulesOperations` operations""" - toolsets: BetaToolsetsOperations - """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - # Replace with patched class that includes upload() - self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) - # Replace with patched class that includes begin_update_memories - self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) - - for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): - setattr( - self, - property_name, - _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), - ) - - -__all__: List[str] = [ - "AgentsOperations", - "BetaEvaluationTaxonomiesOperations", - "BetaEvaluatorsOperations", - "BetaInsightsOperations", - "BetaMemoryStoresOperations", - "BetaOperations", - "BetaRedTeamsOperations", - "BetaSchedulesOperations", - "BetaToolsetsOperations", - "ConnectionsOperations", - "DatasetsOperations", - "EvaluationRulesOperations", - "TelemetryOperations", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py deleted file mode 100644 index f2a6dbc62624..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py +++ /dev/null @@ -1,192 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, IO, overload -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator_async import distributed_trace_async -from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset -from ... import models as _models -from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive -from ...operations._patch_agents import ( - _AGENT_OPERATION_FEATURE_HEADERS, - _PREVIEW_FEATURE_REQUIRED_CODE, - _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE, -) - - -class AgentsOperations(GeneratedAgentsOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`agents` attribute. - """ - - @overload - async def create_version( - self, - agent_name: str, - *, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_version( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_version( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace_async - async def create_version( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS - kwargs["headers"] = headers - - try: - return await super().create_version( - agent_name, - body, - definition=definition, - metadata=metadata, - description=description, - **kwargs, - ) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py deleted file mode 100644 index f32515aca2fe..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py +++ /dev/null @@ -1,74 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Any, Optional, Union -from azure.core.tracing.decorator_async import distributed_trace_async - -from ._operations import ConnectionsOperations as ConnectionsOperationsGenerated -from ...models._models import Connection -from ...models._enums import ConnectionType - - -class ConnectionsOperations(ConnectionsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`connections` attribute. - """ - - @distributed_trace_async - async def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: - """Get a connection by name. - - :param name: The name of the resource. Required. - :type name: str - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ~azure.core.exceptions.HttpResponseError: - """ - - if include_credentials: - connection = await super()._get_with_credentials(name, **kwargs) - if connection.type == ConnectionType.CUSTOM: - # Why do we do this? See comment in the sync version of this code (file _patch_connections.py). - setattr( - connection.credentials, - "credential_keys", - {k: v for k, v in connection.credentials.as_dict().items() if k != "type"}, - ) - - return connection - - return await super()._get(name, **kwargs) - - @distributed_trace_async - async def get_default( - self, connection_type: Union[str, ConnectionType], *, include_credentials: Optional[bool] = False, **kwargs: Any - ) -> Connection: - """Get the default connection for a given connection type. - - :param connection_type: The type of the connection. Required. - :type connection_type: str or ~azure.ai.projects.models.ConnectionType - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ValueError: If no default connection is found for the given type. - :raises ~azure.core.exceptions.HttpResponseError: - """ - connections = super().list(connection_type=connection_type, default_connection=True, **kwargs) - async for connection in connections: - return await self.get(connection.name, include_credentials=include_credentials, **kwargs) - raise ValueError(f"No default connection found for type: {connection_type}.") diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py deleted file mode 100644 index dc7095c827ea..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py +++ /dev/null @@ -1,223 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import re -import logging -from typing import Any, Tuple, Optional -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob.aio import ContainerClient -from azure.core.tracing.decorator_async import distributed_trace_async - -from ._operations import DatasetsOperations as DatasetsOperationsGenerated -from ...models._models import ( - FileDatasetVersion, - FolderDatasetVersion, - PendingUploadRequest, - PendingUploadResponse, - PendingUploadType, -) - -logger = logging.getLogger(__name__) - - -class DatasetsOperations(DatasetsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`datasets` attribute. - """ - - # Internal helper method to create a new dataset and return a ContainerClient from azure-storage-blob package, - # to the dataset's blob storage. - async def _create_dataset_and_get_its_container_client( - self, - name: str, - input_version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str]: - - pending_upload_response: PendingUploadResponse = await self.pending_upload( - name=name, - version=input_version, - pending_upload_request=PendingUploadRequest( - pending_upload_type=PendingUploadType.BLOB_REFERENCE, - connection_name=connection_name, - ), - ) - output_version: str = input_version - - if not pending_upload_response.blob_reference: - raise ValueError("Blob reference is not present") - if not pending_upload_response.blob_reference.credential: - raise ValueError("SAS credential are not present") - if not pending_upload_response.blob_reference.credential.sas_uri: - raise ValueError("SAS URI is missing or empty") - - # For overview on Blob storage SDK in Python see: - # https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python - # https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-python - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-from-container-url - return ( - ContainerClient.from_container_url( - container_url=pending_upload_response.blob_reference.credential.sas_uri, # Of the form: "https://.blob.core.windows.net/?" - ), - output_version, - ) - - @distributed_trace_async - async def upload_file( - self, *, name: str, version: str, file_path: str, connection_name: Optional[str] = None, **kwargs: Any - ) -> FileDatasetVersion: - """Upload file to a blob storage, and create a dataset that references this file. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload the file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword file_path: The file name (including optional path) to be uploaded. Required. - :paramtype file_path: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FileDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - - pathlib_file_path = Path(file_path) - if not pathlib_file_path.exists(): - raise ValueError(f"The provided file `{file_path}` does not exist.") - if pathlib_file_path.is_dir(): - raise ValueError("The provided file is actually a folder. Use method `upload_folder` instead") - - container_client, output_version = await self._create_dataset_and_get_its_container_client( - name=name, - input_version=version, - connection_name=connection_name, - ) - - async with container_client: - - with open(file=file_path, mode="rb") as data: # TODO: What is the best async options for file reading? - - blob_name = pathlib_file_path.name # Extract the file name from the path. - logger.debug( - "[upload_file] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-upload-blob - async with await container_client.upload_blob(name=blob_name, data=data, **kwargs) as blob_client: - logger.debug("[upload_file] Done uploading") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net//" - data_uri = urlsplit(blob_client.url)._replace(query="").geturl() - - dataset_version = await self.create_or_update( - name=name, - version=output_version, - dataset_version=FileDatasetVersion( - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#azure-storage-blob-blobclient-url - # Per above doc the ".url" contains SAS token... should this be stripped away? - data_uri=data_uri, - ), - ) - - return dataset_version # type: ignore - - @distributed_trace_async - async def upload_folder( - self, - *, - name: str, - version: str, - folder: str, - connection_name: Optional[str] = None, - file_pattern: Optional[re.Pattern] = None, - **kwargs: Any, - ) -> FolderDatasetVersion: - """Upload all files in a folder and its sub folders to a blob storage, while maintaining - relative paths, and create a dataset that references this folder. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload each file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword folder: The folder name (including optional path) to be uploaded. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :keyword file_pattern: A regex pattern to filter files to be uploaded. Only files matching the pattern - will be uploaded. Optional. - :paramtype file_pattern: re.Pattern - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FolderDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not Path(path_folder).exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if Path(path_folder).is_file(): - raise ValueError("The provided folder is actually a file. Use method `upload_file` instead.") - - container_client, output_version = await self._create_dataset_and_get_its_container_client( - name=name, input_version=version, connection_name=connection_name - ) - - async with container_client: - - # Recursively traverse all files in the folder - files_uploaded: bool = False - for root, _, files in os.walk(folder): - for file in files: - if file_pattern and not file_pattern.search(file): - continue # Skip files that do not match the pattern - file_path = os.path.join(root, file) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") # Ensure correct format for Azure - logger.debug( - "[upload_folder] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - with open(file=file_path, mode="rb") as data: # Open the file for reading in binary mode - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-upload-blob - await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - logger.debug("[upload_folder] Done uploading file") - files_uploaded = True - logger.debug("[upload_folder] Done uploaded.") - - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net/" - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-url - data_uri = urlsplit(container_client.url)._replace(query="").geturl() - - dataset_version = await self.create_or_update( - name=name, - version=output_version, - dataset_version=FolderDatasetVersion(data_uri=data_uri), - ) - - return dataset_version # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py deleted file mode 100644 index a296493c469b..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py +++ /dev/null @@ -1,129 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Any, IO, overload -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator_async import distributed_trace_async -from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON -from ... import models as _models -from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE -from ...models._enums import _FoundryFeaturesOptInKeys -from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive - - -class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`evaluation_rules` attribute. - """ - - @overload - async def create_or_update( - self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_or_update( - self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_or_update( - self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace_async - async def create_or_update( - self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Is one of the following types: - EvaluationRule, JSON, IO[bytes] Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - } - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - kwargs["headers"] = headers - - try: - return await super().create_or_update(id, evaluation_rule, **kwargs) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py deleted file mode 100644 index c6c366fd5956..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ /dev/null @@ -1,258 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import logging -from typing import Any, Final, IO, Tuple, Optional, Union -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob.aio import ContainerClient -from azure.core.tracing.decorator_async import distributed_trace_async -from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ...models._enums import _FoundryFeaturesOptInKeys -from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME -from ...models._models import ( - CodeBasedEvaluatorDefinition, - EvaluatorVersion, -) - -logger = logging.getLogger(__name__) - -_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - - -class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`beta.evaluators` attribute. - """ - - @staticmethod - async def _upload_folder_to_blob( - container_client: ContainerClient, - folder: str, - **kwargs: Any, - ) -> None: - """Walk *folder* and upload every eligible file to the blob container. - - Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` - directories and ``.pyc`` / ``.pyo`` files. - - :param container_client: The blob container client to upload files to. - :type container_client: ~azure.storage.blob.ContainerClient - :param folder: Path to the local folder containing files to upload. - :type folder: str - :raises ValueError: If the folder contains no uploadable files. - :raises HttpResponseError: Re-raised with a friendlier message on - ``AuthorizationPermissionMismatch``. - """ - skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} - skip_extensions = {".pyc", ".pyo"} - files_uploaded = False - - for root, dirs, files in os.walk(folder): - dirs[:] = [d for d in dirs if d not in skip_dirs] - for file_name in files: - if any(file_name.endswith(ext) for ext in skip_extensions): - continue - file_path = os.path.join(root, file_name) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") - logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) - with open(file=file_path, mode="rb") as data: - try: - await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - except HttpResponseError as e: - if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": - storage_account = urlsplit(container_client.url).hostname - raise HttpResponseError( - message=( - f"Failed to upload file '{blob_name}' to blob storage: " - f"permission denied. Ensure the identity that signed the SAS token " - f"has the 'Storage Blob Data Contributor' role on the storage account " - f"'{storage_account}'. " - f"Original error: {e.message}" - ), - response=e.response, - ) from e - raise - logger.debug("[upload] Done uploading file") - files_uploaded = True - - logger.debug("[upload] Done uploading all files.") - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - @staticmethod - def _set_blob_uri( - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - blob_uri: str, - ) -> None: - """Set ``blob_uri`` on the evaluator version's definition. - - :param evaluator_version: The evaluator version object to update. - :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] - :param blob_uri: The blob URI to set on the definition. - :type blob_uri: str - """ - if isinstance(evaluator_version, dict): - definition = evaluator_version.get("definition", {}) - if isinstance(definition, dict): - definition["blob_uri"] = blob_uri - elif isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - elif isinstance(evaluator_version, EvaluatorVersion): - definition = evaluator_version.definition - if isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - - async def _start_pending_upload_and_get_container_client( - self, - name: str, - version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str, str]: - """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. - - :param name: The evaluator name. - :type name: str - :param version: The evaluator version. - :type version: str - :param connection_name: Optional storage account connection name. - :type connection_name: Optional[str] - :return: A tuple of (ContainerClient, version, blob_uri). - :rtype: Tuple[ContainerClient, str, str] - """ - - request_body: dict = {} - if connection_name: - request_body["connectionName"] = connection_name - - pending_upload_response = await self.pending_upload( - name=name, - version=version, - pending_upload_request=request_body, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - # The service returns blobReferenceForConsumption - blob_ref = pending_upload_response.get("blobReferenceForConsumption") - if not blob_ref: - raise ValueError("Blob reference is not present in the pending upload response") - - credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None - if not credential: - raise ValueError("SAS credential is not present in the pending upload response") - - sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None - if not sas_uri: - raise ValueError("SAS URI is missing or empty in the pending upload response") - - blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None - if not blob_uri: - raise ValueError("Blob URI is missing or empty in the pending upload response") - - return ( - ContainerClient.from_container_url(container_url=sas_uri), - version, - blob_uri, - ) - - async def _get_next_version(self, name: str) -> str: - """Get the next version number for an evaluator by fetching existing versions. - - :param name: The evaluator name. - :type name: str - :return: The next version number as a string. - :rtype: str - """ - try: - versions = [] - async for v in self.list_versions( - name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} - ): - versions.append(v) - if versions: - numeric_versions = [] - for v in versions: - ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) - if ver and ver.isdigit(): - numeric_versions.append(int(ver)) - if numeric_versions: - return str(max(numeric_versions) + 1) - return "1" - except ResourceNotFoundError: - return "1" - - @distributed_trace_async - async def upload( - self, - name: str, - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - *, - folder: str, - connection_name: Optional[str] = None, - **kwargs: Any, - ) -> EvaluatorVersion: - """Upload all files in a folder to blob storage and create a code-based evaluator version - that references the uploaded code. - - This method calls startPendingUpload to get a SAS URI, uploads files from the folder - to blob storage, then creates an evaluator version referencing the uploaded blob. - - The version is automatically determined by incrementing the latest existing version. - - :param name: The name of the evaluator. Required. - :type name: str - :param evaluator_version: The evaluator version definition. This is the same object accepted - by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, - IO[bytes]. Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :keyword folder: Path to the folder containing the evaluator Python code. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection where the files - should be uploaded. If not specified, the default Azure Storage Account connection will be - used. Optional. - :paramtype connection_name: str - :return: The created evaluator version. - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not path_folder.exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if path_folder.is_file(): - raise ValueError("The provided path is a file, not a folder.") - - version = await self._get_next_version(name) - logger.info("[upload] Auto-resolved version to '%s'.", version) - - # Get SAS URI via startPendingUpload - container_client, _, blob_uri = await self._start_pending_upload_and_get_container_client( - name=name, - version=version, - connection_name=connection_name, - ) - - async with container_client: - await self._upload_folder_to_blob(container_client, folder, **kwargs) - self._set_blob_uri(evaluator_version, blob_uri) - - result = await self.create_version( - name=name, - evaluator_version=evaluator_version, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py deleted file mode 100644 index 856d3433a6a7..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py +++ /dev/null @@ -1,379 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, overload, IO, cast -from openai.types.responses import ResponseInputParam -from azure.core.tracing.decorator_async import distributed_trace_async -from azure.core.polling import AsyncNoPolling -from azure.core.utils import case_insensitive_dict -from ... import models as _models -from ...models import ( - MemoryStoreOperationUsage, - ResponseUsageInputTokensDetails, - ResponseUsageOutputTokensDetails, - MemoryStoreUpdateCompletedResult, - AsyncUpdateMemoriesLROPoller, -) -from ...models._patch import ( - _AsyncUpdateMemoriesLROPollingMethod, - _FOUNDRY_FEATURES_HEADER_NAME, - _BETA_OPERATION_FEATURE_HEADERS, -) -from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations -from ...operations._patch_memories import _serialize_memory_input_items -from ..._validation import api_version_validation -from ..._utils.model_base import _deserialize - - -class BetaMemoryStoresOperations(GenerateBetaMemoryStoresOperations): - - @overload - async def search_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def search_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def search_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - async def search_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - return await super()._search_memories( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_search_id=previous_search_id, - options=options, - **kwargs, - ) - - @overload - async def begin_update_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def begin_update_memories( - self, - name: str, - body: JSON, - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def begin_update_memories( - self, - name: str, - body: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - @api_version_validation( - method_added_on="v1", - params_added_on={"v1": ["api_version", "name", "content_type", "accept"]}, - api_versions_list=["v1"], - ) - async def begin_update_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :return: An instance of AsyncLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling = kwargs.pop("polling", True) - if not isinstance(polling, bool): - raise TypeError("polling must be of type bool.") - lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) - cont_token: Optional[str] = kwargs.pop("continuation_token", None) - if cont_token is None: - raw_result = await self._update_memories_initial( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_update_id=previous_update_id, - update_delay=update_delay, - content_type=content_type, - cls=lambda x, y, z: x, - headers=_headers, - params=_params, - **kwargs, - ) - await raw_result.http_response.read() # type: ignore - - raw_result.http_response.status_code = 202 # type: ignore - raw_result.http_response.headers["Operation-Location"] = ( # type: ignore - f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=v1" # type: ignore - ) - - kwargs.pop("error_map", None) - - def get_long_running_output(pipeline_response): - response_headers = {} - response = pipeline_response.http_response - response_headers["Operation-Location"] = self._deserialize( - "str", response.headers.get("Operation-Location") - ) - - deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) - if deserialized is None: - usage = MemoryStoreOperationUsage( - embedding_tokens=0, - input_tokens=0, - input_tokens_details=ResponseUsageInputTokensDetails(cached_tokens=0), - output_tokens=0, - output_tokens_details=ResponseUsageOutputTokensDetails(reasoning_tokens=0), - total_tokens=0, - ) - deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - return deserialized - - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - - if polling: - polling_method: _AsyncUpdateMemoriesLROPollingMethod = _AsyncUpdateMemoriesLROPollingMethod( - lro_delay, - path_format_arguments=path_format_arguments, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, - **kwargs, - ) - else: - polling_method = cast(_AsyncUpdateMemoriesLROPollingMethod, AsyncNoPolling()) - - if cont_token: - return AsyncUpdateMemoriesLROPoller.from_continuation_token( - polling_method=polling_method, - continuation_token=cont_token, - client=self._client, - deserialization_callback=get_long_running_output, - ) - - return AsyncUpdateMemoriesLROPoller( - self._client, - raw_result, # type: ignore[possibly-undefined] - get_long_running_output, - polling_method, # pylint: disable=possibly-used-before-assignment - ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py deleted file mode 100644 index 7c50fb95ca96..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py +++ /dev/null @@ -1,75 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Optional, AsyncIterable -from azure.core.exceptions import ResourceNotFoundError -from azure.core.tracing.decorator_async import distributed_trace_async - -from ...models._models import ( - Connection, - ApiKeyCredentials, -) -from ...models._enums import ConnectionType - - -class TelemetryOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`telemetry` attribute. - """ - - _connection_string: Optional[str] = None - - def __init__(self, outer_instance: "azure.ai.projects.aio.AIProjectClient") -> None: # type: ignore[name-defined] - self._outer_instance = outer_instance - - @distributed_trace_async - async def get_application_insights_connection_string(self) -> str: # pylint: disable=name-too-long - """Get the Application Insights connection string associated with the Project's Application Insights resource. - - :return: The Application Insights connection string if a the resource was enabled for the Project. - :rtype: str - :raises ~azure.core.exceptions.ResourceNotFoundError: An Application Insights connection does not - exist for this Foundry project. - """ - if not self._connection_string: - - # TODO: Two REST APIs calls can be replaced by one if we have had REST API for get_with_credentials(connection_type=ConnectionType.APPLICATION_INSIGHTS) - # Returns an empty Iterable if no connections exits. - connections: AsyncIterable[Connection] = self._outer_instance.connections.list( - connection_type=ConnectionType.APPLICATION_INSIGHTS, - ) - - # Note: there can't be more than one AppInsights connection. - connection_name: Optional[str] = None - async for connection in connections: - connection_name = connection.name - break - if not connection_name: - raise ResourceNotFoundError("No Application Insights connection found.") - - connection = ( - await self._outer_instance.connections._get_with_credentials( # pylint: disable=protected-access - name=connection_name - ) - ) - - if isinstance(connection.credentials, ApiKeyCredentials): - if not connection.credentials.api_key: - raise ValueError("Application Insights connection does not have a connection string.") - self._connection_string = connection.credentials.api_key - else: - raise ValueError("Application Insights connection does not use API Key credentials.") - - return self._connection_string diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index 2c528dea5d0d..eaf5ee2de82e 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -550,8 +550,8 @@ class RankerVersionType(str, Enum, metaclass=CaseInsensitiveEnumMeta): AUTO = "auto" """AUTO.""" - DEFAULT_2024_11_15 = "default-2024-11-15" - """DEFAULT_2024_11_15.""" + DEFAULT2024_11_15 = "default-2024-11-15" + """DEFAULT2024_11_15.""" class RecurrenceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): @@ -666,8 +666,8 @@ class ToolChoiceParamType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """WEB_SEARCH_PREVIEW.""" COMPUTER_USE_PREVIEW = "computer_use_preview" """COMPUTER_USE_PREVIEW.""" - WEB_SEARCH_PREVIEW_2025_03_11 = "web_search_preview_2025_03_11" - """WEB_SEARCH_PREVIEW_2025_03_11.""" + WEB_SEARCH_PREVIEW2025_03_11 = "web_search_preview_2025_03_11" + """WEB_SEARCH_PREVIEW2025_03_11.""" IMAGE_GENERATION = "image_generation" """IMAGE_GENERATION.""" CODE_INTERPRETER = "code_interpreter" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index c9a06e9dacfa..f70f9d97a8d9 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -8635,7 +8635,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): or a Literal["required"] type. :vartype mode: str or str :ivar tools: A list of tool definitions that the model should be allowed to call. For the - Responses API, the list of tool definitions might look like the following. Required. + Responses API, the list of tool definitions might look like: .. code-block:: json @@ -8643,7 +8643,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): { "type": "function", "name": "get_weather" }, { "type": "mcp", "server_label": "deepwiki" }, { "type": "image_generation" } - ] + ]. Required. :vartype tools: list[dict[str, any]] """ @@ -8656,7 +8656,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): Literal[\"required\"] type.""" tools: list[dict[str, Any]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of tool definitions that the model should be allowed to call. For the Responses API, the - list of tool definitions might look like the following. Required. + list of tool definitions might look like: .. code-block:: json @@ -8664,7 +8664,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): { \"type\": \"function\", \"name\": \"get_weather\" }, { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, { \"type\": \"image_generation\" } - ]""" + ]. Required.""" @overload def __init__( @@ -8933,12 +8933,12 @@ class ToolChoiceWebSearchPreview20250311(ToolChoiceParam, discriminator="web_sea """Indicates that the model should use a built-in tool to generate a response. `Learn more about built-in tools `_. - :ivar type: Required. WEB_SEARCH_PREVIEW_2025_03_11. - :vartype type: str or ~azure.ai.projects.models.WEB_SEARCH_PREVIEW_2025_03_11 + :ivar type: Required. WEB_SEARCH_PREVIEW2025_03_11. + :vartype type: str or ~azure.ai.projects.models.WEB_SEARCH_PREVIEW2025_03_11 """ - type: Literal[ToolChoiceParamType.WEB_SEARCH_PREVIEW_2025_03_11] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore - """Required. WEB_SEARCH_PREVIEW_2025_03_11.""" + type: Literal[ToolChoiceParamType.WEB_SEARCH_PREVIEW2025_03_11] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. WEB_SEARCH_PREVIEW2025_03_11.""" @overload def __init__( @@ -8954,7 +8954,7 @@ def __init__(self, mapping: Mapping[str, Any]) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.type = ToolChoiceParamType.WEB_SEARCH_PREVIEW_2025_03_11 # type: ignore + self.type = ToolChoiceParamType.WEB_SEARCH_PREVIEW2025_03_11 # type: ignore class ToolDescription(_Model): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index 658e4c90fb2f..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -1,355 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import Final, FrozenSet, List, Dict, Mapping, Optional, Any, Tuple -from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod -from azure.core.polling.base_polling import ( - LROBasePolling, - OperationFailed, - _raise_if_bad_http_status_and_method, -) -from azure.core.polling.async_base_polling import AsyncLROBasePolling -from ._models import CustomCredential as CustomCredentialGenerated -from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult -from ._enums import _FoundryFeaturesOptInKeys -_FOUNDRY_FEATURES_HEADER_NAME: Final[str] = "Foundry-Features" -"""The HTTP header name used to opt in to Foundry preview features.""" - -_BETA_OPERATION_FEATURE_HEADERS: Final[dict] = { - "evaluation_taxonomies": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "evaluators": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "insights": _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, - "memory_stores": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, - "red_teams": _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, - "schedules": _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, - "toolsets": _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, -} -"""Foundry-Features header values keyed by beta sub-client property name.""" - - -def _has_header_case_insensitive(headers: Any, header_name: str) -> bool: - """Return True if headers already contains the provided header name. - - :param headers: The headers mapping to search. - :type headers: Any - :param header_name: The header name to look for (case-insensitive). - :type header_name: str - :return: True if the header is present, False otherwise. - :rtype: bool - """ - try: - header_name_lower = header_name.lower() - return any(str(key).lower() == header_name_lower for key in headers) - except Exception: # pylint: disable=broad-except - return False - - -class CustomCredential(CustomCredentialGenerated, discriminator="CustomKeys"): - """Custom credential definition. - - :ivar type: The credential type. Always equals CredentialType.CUSTOM. Required. - :vartype type: str or ~azure.ai.projects.models.CredentialType - :ivar credential_keys: The secret custom credential keys. Required. - :vartype credential_keys: dict[str, str] - """ - - credential_keys: Dict[str, str] - """The secret custom credential keys. Required.""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - - # Fix for GitHub issue https://github.com/Azure/azure-sdk-for-net/issues/52355 - # Although the issue was filed on C# Projects SDK, the same problem exists in Python SDK. - # Assume your Foundry project has a connection of type `Custom`, named "test_custom_connection", - # and you defined two public and two secrete (private) keys. When you get the connection, the response - # payload will look something like this: - # { - # "name": "test_custom_connection", - # "id": "/subscriptions/.../connections/test_custom_connection", - # "type": "CustomKeys", - # "target": "_", - # "isDefault": true, - # "credentials": { - # "nameofprivatekey1": "PrivateKey1", - # "nameofprivatekey2": "PrivateKey2", - # "type": "CustomKeys" - # }, - # "metadata": { - # "NameOfPublicKey1": "PublicKey1", - # "NameOfPublicKey2": "PublicKey2" - # } - # } - # We would like to add a new Dict property on the Python `credentials` object, named `credential_keys`, - # to hold all the secret keys. This is done by the line below. - if args and isinstance(args[0], Mapping): - self.credential_keys = {k: v for k, v in args[0].items() if k != "type"} - else: - self.credential_keys = {} - - -_FINISHED: Final[FrozenSet[str]] = frozenset(["completed", "superseded", "failed"]) -_FAILED: Final[FrozenSet[str]] = frozenset(["failed"]) - - -class _UpdateMemoriesLROPollingMethod(LROBasePolling): - """A custom polling method implementation for Memory Store updates.""" - - @property - def _current_body(self) -> MemoryStoreUpdateResult: - try: - return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) - except Exception: # pylint: disable=broad-exception-caught - return MemoryStoreUpdateResult() # type: ignore[call-overload] - - def finished(self) -> bool: - """Is this polling finished? - - :return: True/False for whether polling is complete. - :rtype: bool - """ - return self._finished(self.status()) - - @staticmethod - def _finished(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FINISHED - - @staticmethod - def _failed(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FAILED - - def get_continuation_token(self) -> str: - return self._current_body.update_id - - # pylint: disable=arguments-differ - def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] - try: - client = kwargs["client"] - except KeyError as exc: - raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc - - try: - deserialization_callback = kwargs["deserialization_callback"] - except KeyError as exc: - raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc - - return client, continuation_token, deserialization_callback - - def _poll(self) -> None: - """Poll status of operation so long as operation is incomplete and - we have an endpoint to query. - - :raises: OperationFailed if operation status 'Failed' or 'Canceled'. - :raises: BadStatus if response status invalid. - :raises: BadResponse if response invalid. - """ - - if not self.finished(): - self.update_status() - while not self.finished(): - self._delay() - self.update_status() - - if self._failed(self.status()): - raise OperationFailed("Operation failed or canceled") - - final_get_url = self._operation.get_final_get_url(self._pipeline_response) - if final_get_url: - self._pipeline_response = self.request_status(final_get_url) - _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) - - -class _AsyncUpdateMemoriesLROPollingMethod(AsyncLROBasePolling): - """A custom polling method implementation for Memory Store updates.""" - - @property - def _current_body(self) -> MemoryStoreUpdateResult: - try: - return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) - except Exception: # pylint: disable=broad-exception-caught - return MemoryStoreUpdateResult() # type: ignore[call-overload] - - def finished(self) -> bool: - """Is this polling finished? - - :return: True/False for whether polling is complete. - :rtype: bool - """ - return self._finished(self.status()) - - @staticmethod - def _finished(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FINISHED - - @staticmethod - def _failed(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FAILED - - def get_continuation_token(self) -> str: - return self._current_body.update_id - - # pylint: disable=arguments-differ - def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] - try: - client = kwargs["client"] - except KeyError as exc: - raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc - - try: - deserialization_callback = kwargs["deserialization_callback"] - except KeyError as exc: - raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc - - return client, continuation_token, deserialization_callback - - async def _poll(self) -> None: - """Poll status of operation so long as operation is incomplete and - we have an endpoint to query. - - :raises: OperationFailed if operation status 'Failed' or 'Canceled'. - :raises: BadStatus if response status invalid. - :raises: BadResponse if response invalid. - """ - - if not self.finished(): - await self.update_status() - while not self.finished(): - await self._delay() - await self.update_status() - - if self._failed(self.status()): - raise OperationFailed("Operation failed or canceled") - - final_get_url = self._operation.get_final_get_url(self._pipeline_response) - if final_get_url: - self._pipeline_response = await self.request_status(final_get_url) - _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) - - -class UpdateMemoriesLROPoller(LROPoller[MemoryStoreUpdateCompletedResult]): - """Custom LROPoller for Memory Store update operations.""" - - _polling_method: "_UpdateMemoriesLROPollingMethod" - - @property - def update_id(self) -> str: - """Returns the update ID associated with the long-running update memories operation. - - :return: Returns the update ID. - :rtype: str - """ - return self._polling_method._current_body.update_id # pylint: disable=protected-access - - @property - def superseded_by(self) -> Optional[str]: - """Returns the ID of the operation that superseded this update. - - :return: Returns the ID of the superseding operation, if it exists. - :rtype: Optional[str] - """ - return ( - self._polling_method._current_body.superseded_by # pylint: disable=protected-access - if self._polling_method._current_body # pylint: disable=protected-access - else None - ) - - @classmethod - def from_continuation_token( - cls, polling_method: PollingMethod[MemoryStoreUpdateCompletedResult], continuation_token: str, **kwargs: Any - ) -> "UpdateMemoriesLROPoller": - """Create a poller from a continuation token. - - :param polling_method: The polling strategy to adopt - :type polling_method: ~azure.core.polling.PollingMethod - :param continuation_token: An opaque continuation token - :type continuation_token: str - :return: An instance of UpdateMemoriesLROPoller - :rtype: UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. - """ - ( - client, - initial_response, - deserialization_callback, - ) = polling_method.from_continuation_token(continuation_token, **kwargs) - - return cls(client, initial_response, deserialization_callback, polling_method) - - -class AsyncUpdateMemoriesLROPoller(AsyncLROPoller[MemoryStoreUpdateCompletedResult]): - """Custom AsyncLROPoller for Memory Store update operations.""" - - _polling_method: "_AsyncUpdateMemoriesLROPollingMethod" - - @property - def update_id(self) -> str: - """Returns the update ID associated with the long-running update memories operation. - - :return: Returns the update ID. - :rtype: str - """ - return self._polling_method._current_body.update_id # pylint: disable=protected-access - - @property - def superseded_by(self) -> Optional[str]: - """Returns the ID of the operation that superseded this update. - - :return: Returns the ID of the superseding operation, if it exists. - :rtype: Optional[str] - """ - return ( - self._polling_method._current_body.superseded_by # pylint: disable=protected-access - if self._polling_method._current_body # pylint: disable=protected-access - else None - ) - - @classmethod - def from_continuation_token( - cls, - polling_method: AsyncPollingMethod[MemoryStoreUpdateCompletedResult], - continuation_token: str, - **kwargs: Any, - ) -> "AsyncUpdateMemoriesLROPoller": - """Create a poller from a continuation token. - - :param polling_method: The polling strategy to adopt - :type polling_method: ~azure.core.polling.PollingMethod - :param continuation_token: An opaque continuation token - :type continuation_token: str - :return: An instance of AsyncUpdateMemoriesLROPoller - :rtype: AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. - """ - ( - client, - initial_response, - deserialization_callback, - ) = polling_method.from_continuation_token(continuation_token, **kwargs) - - return cls(client, initial_response, deserialization_callback, polling_method) - - -__all__: List[str] = [ - "CustomCredential", - "UpdateMemoriesLROPoller", - "AsyncUpdateMemoriesLROPoller", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index f320a58a57b0..d5b154dc719b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -4799,10 +4799,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5232,10 +5229,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5335,10 +5329,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6391,10 +6382,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7062,7 +7050,7 @@ def _search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -7148,7 +7136,7 @@ def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, @@ -7595,10 +7583,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7942,10 +7927,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -8245,10 +8227,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index 2330bf816c59..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -1,142 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from functools import wraps -import inspect -from typing import Any, Callable, List -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _BETA_OPERATION_FEATURE_HEADERS, _has_header_case_insensitive -from ._patch_agents import AgentsOperations -from ._patch_datasets import DatasetsOperations -from ._patch_evaluation_rules import EvaluationRulesOperations -from ._patch_evaluators import BetaEvaluatorsOperations -from ._patch_telemetry import TelemetryOperations -from ._patch_connections import ConnectionsOperations -from ._patch_memories import BetaMemoryStoresOperations -from ._operations import ( - BetaEvaluationTaxonomiesOperations, - BetaInsightsOperations, - BetaOperations as GeneratedBetaOperations, - BetaRedTeamsOperations, - BetaSchedulesOperations, - BetaToolsetsOperations, -) - -def _method_accepts_keyword_headers(method: Callable[..., Any]) -> bool: - try: - signature = inspect.signature(method) - except (TypeError, ValueError): - return False - - for parameter in signature.parameters.values(): - if parameter.name == "headers": - return True - if parameter.kind == inspect.Parameter.VAR_KEYWORD: - return True - - return False - - -class _OperationMethodHeaderProxy: - """Proxy that injects the Foundry-Features header into public operation method calls.""" - - def __init__(self, operation: Any, foundry_features_value: str): - object.__setattr__(self, "_operation", operation) - object.__setattr__(self, "_foundry_features_value", foundry_features_value) - - def __getattr__(self, name: str) -> Any: - attribute = getattr(self._operation, name) - - if name.startswith("_") or not callable(attribute) or not _method_accepts_keyword_headers(attribute): - return attribute - - @wraps(attribute) - def _wrapped(*args: Any, **kwargs: Any) -> Any: - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value} - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - try: - headers[_FOUNDRY_FEATURES_HEADER_NAME] = self._foundry_features_value - except Exception: # pylint: disable=broad-except - # Fall back to replacing invalid/immutable header containers. - kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value, - } - - return attribute(*args, **kwargs) - - return _wrapped - - def __dir__(self) -> list: - return dir(self._operation) - - def __setattr__(self, name: str, value: Any) -> None: - setattr(self._operation, name, value) - - -class BetaOperations(GeneratedBetaOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`beta` attribute. - """ - - evaluation_taxonomies: BetaEvaluationTaxonomiesOperations - """:class:`~azure.ai.projects.operations.BetaEvaluationTaxonomiesOperations` operations""" - evaluators: BetaEvaluatorsOperations - """:class:`~azure.ai.projects.operations.BetaEvaluatorsOperations` operations""" - insights: BetaInsightsOperations - """:class:`~azure.ai.projects.operations.BetaInsightsOperations` operations""" - memory_stores: BetaMemoryStoresOperations - """:class:`~azure.ai.projects.operations.BetaMemoryStoresOperations` operations""" - red_teams: BetaRedTeamsOperations - """:class:`~azure.ai.projects.operations.BetaRedTeamsOperations` operations""" - schedules: BetaSchedulesOperations - """:class:`~azure.ai.projects.operations.BetaSchedulesOperations` operations""" - toolsets: BetaToolsetsOperations - """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - # Replace with patched class that includes upload() - self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) - # Replace with patched class that includes begin_update_memories - self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) - - for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): - setattr( - self, - property_name, - _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), - ) - - -__all__: List[str] = [ - "AgentsOperations", - "BetaEvaluationTaxonomiesOperations", - "BetaEvaluatorsOperations", - "BetaInsightsOperations", - "BetaMemoryStoresOperations", - "BetaOperations", - "BetaRedTeamsOperations", - "BetaSchedulesOperations", - "BetaToolsetsOperations", - "ConnectionsOperations", - "DatasetsOperations", - "EvaluationRulesOperations", - "TelemetryOperations", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py deleted file mode 100644 index 8c19e6279729..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py +++ /dev/null @@ -1,218 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression,pointless-string-statement -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, IO, overload, Final -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator import distributed_trace -from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset -from .. import models as _models -from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive - -""" -Example service response payload when the caller is trying to use a feature preview without opt-in flag (service error 403 (Forbidden)): - -"error": { - "code": "preview_feature_required", - "message": "Workflow agents is in preview. This operation requires the following opt-in preview feature(s): WorkflowAgents=V1Preview. Include the 'Foundry-Features: WorkflowAgents=V1Preview' header in your request.", - "param": "Foundry-Features", - "type": "invalid_request_error", - "details": [], - "additionalInfo": { - "request_id": "fdbc95804b7599404973026cd9ec732a" - } - } - -""" -_PREVIEW_FEATURE_REQUIRED_CODE: Final = "preview_feature_required" -_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE: Final = ( - '\n**Python SDK users**: This operation requires you to set "allow_preview=True" ' - "when calling the AIProjectClient constructor. " - "\nNote that preview features are under development and subject to change." -) -_AGENT_OPERATION_FEATURE_HEADERS: Final[str] = ",".join( - [ - _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, - _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, - _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, - ] -) - - -class AgentsOperations(GeneratedAgentsOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`agents` attribute. - """ - - @overload - def create_version( - self, - agent_name: str, - *, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_version( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_version( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace - def create_version( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS - kwargs["headers"] = headers - - try: - return super().create_version( - agent_name, - body, - definition=definition, - metadata=metadata, - description=description, - **kwargs, - ) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py deleted file mode 100644 index 581d13671516..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py +++ /dev/null @@ -1,63 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Optional, Any, Union -from azure.core.tracing.decorator import distributed_trace -from ._operations import ConnectionsOperations as ConnectionsOperationsGenerated -from ..models._models import Connection -from ..models._enums import ConnectionType - - -class ConnectionsOperations(ConnectionsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`connections` attribute. - """ - - @distributed_trace - def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: - """Get a connection by name. - - :param name: The name of the connection. Required. - :type name: str - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ~azure.core.exceptions.HttpResponseError: - """ - if include_credentials: - return super()._get_with_credentials(name, **kwargs) - - return super()._get(name, **kwargs) - - @distributed_trace - def get_default( - self, connection_type: Union[str, ConnectionType], *, include_credentials: Optional[bool] = False, **kwargs: Any - ) -> Connection: - """Get the default connection for a given connection type. - - :param connection_type: The type of the connection. Required. - :type connection_type: str or ~azure.ai.projects.models.ConnectionType - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ValueError: If no default connection is found for the given type. - :raises ~azure.core.exceptions.HttpResponseError: - """ - connections = super().list(connection_type=connection_type, default_connection=True, **kwargs) - for connection in connections: - return self.get(connection.name, include_credentials=include_credentials, **kwargs) - raise ValueError(f"No default connection found for type: {connection_type}.") diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py deleted file mode 100644 index bf2c0db51271..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py +++ /dev/null @@ -1,222 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import re -import logging -from typing import Any, Tuple, Optional -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob import ContainerClient -from azure.core.tracing.decorator import distributed_trace -from ._operations import DatasetsOperations as DatasetsOperationsGenerated -from ..models._models import ( - FileDatasetVersion, - FolderDatasetVersion, - PendingUploadRequest, - PendingUploadResponse, - PendingUploadType, -) - -logger = logging.getLogger(__name__) - - -class DatasetsOperations(DatasetsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`datasets` attribute. - """ - - # Internal helper method to create a new dataset and return a ContainerClient from azure-storage-blob package, - # to the dataset's blob storage. - def _create_dataset_and_get_its_container_client( - self, - name: str, - input_version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str]: - - pending_upload_response: PendingUploadResponse = self.pending_upload( - name=name, - version=input_version, - pending_upload_request=PendingUploadRequest( - pending_upload_type=PendingUploadType.BLOB_REFERENCE, - connection_name=connection_name, - ), - ) - output_version: str = input_version - - if not pending_upload_response.blob_reference: - raise ValueError("Blob reference is not present") - if not pending_upload_response.blob_reference.credential: - raise ValueError("SAS credential are not present") - if not pending_upload_response.blob_reference.credential.sas_uri: - raise ValueError("SAS URI is missing or empty") - - # For overview on Blob storage SDK in Python see: - # https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python - # https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-python - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-from-container-url - return ( - ContainerClient.from_container_url( - container_url=pending_upload_response.blob_reference.credential.sas_uri # Of the form: "https://.blob.core.windows.net/?" - ), - output_version, - ) - - @distributed_trace - def upload_file( - self, *, name: str, version: str, file_path: str, connection_name: Optional[str] = None, **kwargs: Any - ) -> FileDatasetVersion: - """Upload file to a blob storage, and create a dataset that references this file. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload the file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword file_path: The file name (including optional path) to be uploaded. Required. - :paramtype file_path: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FileDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - - pathlib_file_path = Path(file_path) - if not pathlib_file_path.exists(): - raise ValueError(f"The provided file `{file_path}` does not exist.") - if pathlib_file_path.is_dir(): - raise ValueError("The provided file is actually a folder. Use method `upload_folder` instead") - - container_client, output_version = self._create_dataset_and_get_its_container_client( - name=name, input_version=version, connection_name=connection_name - ) - - with container_client: - - with open(file=file_path, mode="rb") as data: - - blob_name = pathlib_file_path.name # Extract the file name from the path. - logger.debug( - "[upload_file] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-upload-blob - with container_client.upload_blob(name=blob_name, data=data, **kwargs) as blob_client: - logger.debug("[upload_file] Done uploading") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net//" - data_uri = urlsplit(blob_client.url)._replace(query="").geturl() - - dataset_version = self.create_or_update( - name=name, - version=output_version, - dataset_version=FileDatasetVersion( - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#azure-storage-blob-blobclient-url - # Per above doc the ".url" contains SAS token... should this be stripped away? - data_uri=data_uri, - ), - ) - - return dataset_version # type: ignore - - @distributed_trace - def upload_folder( - self, - *, - name: str, - version: str, - folder: str, - connection_name: Optional[str] = None, - file_pattern: Optional[re.Pattern] = None, - **kwargs: Any, - ) -> FolderDatasetVersion: - """Upload all files in a folder and its sub folders to a blob storage, while maintaining - relative paths, and create a dataset that references this folder. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload each file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword folder: The folder name (including optional path) to be uploaded. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :keyword file_pattern: A regex pattern to filter files to be uploaded. Only files matching the pattern - will be uploaded. Optional. - :paramtype file_pattern: re.Pattern - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FolderDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not Path(path_folder).exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if Path(path_folder).is_file(): - raise ValueError("The provided folder is actually a file. Use method `upload_file` instead.") - - container_client, output_version = self._create_dataset_and_get_its_container_client( - name=name, - input_version=version, - connection_name=connection_name, - ) - - with container_client: - - # Recursively traverse all files in the folder - files_uploaded: bool = False - for root, _, files in os.walk(folder): - for file in files: - if file_pattern and not file_pattern.search(file): - continue # Skip files that do not match the pattern - file_path = os.path.join(root, file) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") # Ensure correct format for Azure - logger.debug( - "[upload_folder] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - with open(file=file_path, mode="rb") as data: # Open the file for reading in binary mode - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-upload-blob - container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - logger.debug("[upload_folder] Done uploading file") - files_uploaded = True - logger.debug("[upload_folder] Done uploaded.") - - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net/" - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-url - data_uri = urlsplit(container_client.url)._replace(query="").geturl() - - dataset_version = self.create_or_update( - name=name, - version=output_version, - dataset_version=FolderDatasetVersion(data_uri=data_uri), - ) - - return dataset_version # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py deleted file mode 100644 index 19dcee1ed6bd..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py +++ /dev/null @@ -1,130 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Any, IO, overload -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator import distributed_trace -from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON -from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE -from .. import models as _models -from ..models._enums import _FoundryFeaturesOptInKeys -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive - - -class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`evaluation_rules` attribute. - """ - - @overload - def create_or_update( - self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_or_update( - self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_or_update( - self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace - def create_or_update( - self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Is one of the following types: - EvaluationRule, JSON, IO[bytes] Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - } - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - kwargs["headers"] = headers - - try: - return super().create_or_update(id, evaluation_rule, **kwargs) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py deleted file mode 100644 index 3f1c38d97b2b..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ /dev/null @@ -1,258 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import logging -from typing import Any, Final, IO, Tuple, Optional, Union -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob import ContainerClient -from azure.core.tracing.decorator import distributed_trace -from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ..models._enums import _FoundryFeaturesOptInKeys -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME -from ..models._models import ( - CodeBasedEvaluatorDefinition, - EvaluatorVersion, -) - -logger = logging.getLogger(__name__) - -_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - - -class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`beta.evaluators` attribute. - """ - - @staticmethod - def _upload_folder_to_blob( - container_client: ContainerClient, - folder: str, - **kwargs: Any, - ) -> None: - """Walk *folder* and upload every eligible file to the blob container. - - Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` - directories and ``.pyc`` / ``.pyo`` files. - - :param container_client: The blob container client to upload files to. - :type container_client: ~azure.storage.blob.ContainerClient - :param folder: Path to the local folder containing files to upload. - :type folder: str - :raises ValueError: If the folder contains no uploadable files. - :raises HttpResponseError: Re-raised with a friendlier message on - ``AuthorizationPermissionMismatch``. - """ - skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} - skip_extensions = {".pyc", ".pyo"} - files_uploaded = False - - for root, dirs, files in os.walk(folder): - dirs[:] = [d for d in dirs if d not in skip_dirs] - for file_name in files: - if any(file_name.endswith(ext) for ext in skip_extensions): - continue - file_path = os.path.join(root, file_name) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") - logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) - with open(file=file_path, mode="rb") as data: - try: - container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - except HttpResponseError as e: - if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": - storage_account = urlsplit(container_client.url).hostname - raise HttpResponseError( - message=( - f"Failed to upload file '{blob_name}' to blob storage: " - f"permission denied. Ensure the identity that signed the SAS token " - f"has the 'Storage Blob Data Contributor' role on the storage account " - f"'{storage_account}'. " - f"Original error: {e.message}" - ), - response=e.response, - ) from e - raise - logger.debug("[upload] Done uploading file") - files_uploaded = True - - logger.debug("[upload] Done uploading all files.") - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - @staticmethod - def _set_blob_uri( - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - blob_uri: str, - ) -> None: - """Set ``blob_uri`` on the evaluator version's definition. - - :param evaluator_version: The evaluator version object to update. - :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] - :param blob_uri: The blob URI to set on the definition. - :type blob_uri: str - """ - if isinstance(evaluator_version, dict): - definition = evaluator_version.get("definition", {}) - if isinstance(definition, dict): - definition["blob_uri"] = blob_uri - elif isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - elif isinstance(evaluator_version, EvaluatorVersion): - definition = evaluator_version.definition - if isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - - def _start_pending_upload_and_get_container_client( - self, - name: str, - version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str, str]: - """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. - - :param name: The evaluator name. - :type name: str - :param version: The evaluator version. - :type version: str - :param connection_name: Optional storage account connection name. - :type connection_name: Optional[str] - :return: A tuple of (ContainerClient, version, blob_uri). - :rtype: Tuple[ContainerClient, str, str] - """ - - request_body: dict = {} - if connection_name: - request_body["connectionName"] = connection_name - - pending_upload_response = self.pending_upload( - name=name, - version=version, - pending_upload_request=request_body, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - # The service returns blobReferenceForConsumption - blob_ref = pending_upload_response.get("blobReferenceForConsumption") - if not blob_ref: - raise ValueError("Blob reference is not present in the pending upload response") - - credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None - if not credential: - raise ValueError("SAS credential is not present in the pending upload response") - - sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None - if not sas_uri: - raise ValueError("SAS URI is missing or empty in the pending upload response") - - blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None - if not blob_uri: - raise ValueError("Blob URI is missing or empty in the pending upload response") - - return ( - ContainerClient.from_container_url(container_url=sas_uri), - version, - blob_uri, - ) - - def _get_next_version(self, name: str) -> str: - """Get the next version number for an evaluator by fetching existing versions. - - :param name: The evaluator name. - :type name: str - :return: The next version number as a string. - :rtype: str - """ - try: - versions = list( - self.list_versions( - name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} - ) - ) - if versions: - numeric_versions = [] - for v in versions: - ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) - if ver and ver.isdigit(): - numeric_versions.append(int(ver)) - if numeric_versions: - return str(max(numeric_versions) + 1) - return "1" - except ResourceNotFoundError: - return "1" - - @distributed_trace - def upload( - self, - name: str, - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - *, - folder: str, - connection_name: Optional[str] = None, - **kwargs: Any, - ) -> EvaluatorVersion: - """Upload all files in a folder to blob storage and create a code-based evaluator version - that references the uploaded code. - - This method calls startPendingUpload to get a SAS URI, uploads files from the folder - to blob storage, then creates an evaluator version referencing the uploaded blob. - - The version is automatically determined by incrementing the latest existing version. - - :param name: The name of the evaluator. Required. - :type name: str - :param evaluator_version: The evaluator version definition. This is the same object accepted - by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, - IO[bytes]. Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :keyword folder: Path to the folder containing the evaluator Python code. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection where the files - should be uploaded. If not specified, the default Azure Storage Account connection will be - used. Optional. - :paramtype connection_name: str - :return: The created evaluator version. - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not path_folder.exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if path_folder.is_file(): - raise ValueError("The provided path is a file, not a folder.") - - version = self._get_next_version(name) - logger.info("[upload] Auto-resolved version to '%s'.", version) - - # Get SAS URI via startPendingUpload - container_client, _, blob_uri = self._start_pending_upload_and_get_container_client( - name=name, - version=version, - connection_name=connection_name, - ) - - with container_client: - self._upload_folder_to_blob(container_client, folder, **kwargs) - self._set_blob_uri(evaluator_version, blob_uri) - - result = self.create_version( - name=name, - evaluator_version=evaluator_version, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py deleted file mode 100644 index 33b932900a35..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py +++ /dev/null @@ -1,414 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, List, overload, IO, cast -from openai.types.responses import ResponseInputParam -from azure.core.tracing.decorator import distributed_trace -from azure.core.polling import NoPolling -from azure.core.utils import case_insensitive_dict -from .. import models as _models -from ..models import ( - MemoryStoreOperationUsage, - ResponseUsageInputTokensDetails, - ResponseUsageOutputTokensDetails, - MemoryStoreUpdateCompletedResult, - UpdateMemoriesLROPoller, -) -from ..models._patch import ( - _UpdateMemoriesLROPollingMethod, - _FOUNDRY_FEATURES_HEADER_NAME, - _BETA_OPERATION_FEATURE_HEADERS, -) -from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations -from .._validation import api_version_validation -from .._utils.model_base import _deserialize, _serialize - - -def _serialize_memory_input_items( - items: Optional[Union[str, ResponseInputParam]], -) -> Optional[List[dict[str, Any]]]: - """Serialize OpenAI response input items to the payload shape expected by memory APIs. - - :param items: The items to serialize. Can be a plain string or an OpenAI ResponseInputParam. - :type items: Optional[Union[str, openai.types.responses.ResponseInputParam]] - :return: A list of serialized item dictionaries, or None if items is None. - :rtype: Optional[List[dict[str, Any]]] - """ - - if items is None: - return None - - if isinstance(items, str): - return [{"role": "user", "type": "message", "content": items}] - - if not isinstance(items, list): - raise TypeError("items must serialize to a list of dictionaries.") - - serialized_items: List[dict[str, Any]] = [] - for item in items: - if hasattr(item, "model_dump"): - item = cast(Any, item).model_dump() - elif hasattr(item, "as_dict"): - item = cast(Any, item).as_dict() - - serialized_item = _serialize(item) - if not isinstance(serialized_item, dict): - raise TypeError("items must serialize to a dictionary .") - serialized_items.append(serialized_item) - return serialized_items - - -class BetaMemoryStoresOperations(GenerateBetaMemoryStoresOperations): - - # A message or list of messages to store in memory. When using a list, each item needs to correspond to a dictionary with `role`, `content` and `type` properties (with type equals `message`). For example: {\"role\": \"user\", \"type\": \"message\", \"content\": \"my user message\"}" - @overload - def search_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def search_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def search_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - def search_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - return super()._search_memories( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_search_id=previous_search_id, - options=options, - **kwargs, - ) - - @overload - def begin_update_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def begin_update_memories( - self, - name: str, - body: JSON, - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def begin_update_memories( - self, - name: str, - body: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - @api_version_validation( - method_added_on="v1", - params_added_on={"v1": ["api_version", "name", "content_type", "accept"]}, - api_versions_list=["v1"], - ) - def begin_update_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling = kwargs.pop("polling", True) - if not isinstance(polling, bool): - raise TypeError("polling must be of type bool.") - lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) - cont_token: Optional[str] = kwargs.pop("continuation_token", None) - if cont_token is None: - raw_result = self._update_memories_initial( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_update_id=previous_update_id, - update_delay=update_delay, - content_type=content_type, - cls=lambda x, y, z: x, - headers=_headers, - params=_params, - **kwargs, - ) - raw_result.http_response.read() # type: ignore - - raw_result.http_response.status_code = 202 # type: ignore - raw_result.http_response.headers["Operation-Location"] = ( # type: ignore - f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=v1" # type: ignore - ) - - kwargs.pop("error_map", None) - - def get_long_running_output(pipeline_response): - response_headers = {} - response = pipeline_response.http_response - response_headers["Operation-Location"] = self._deserialize( - "str", response.headers.get("Operation-Location") - ) - - deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) - if deserialized is None: - usage = MemoryStoreOperationUsage( - embedding_tokens=0, - input_tokens=0, - input_tokens_details=ResponseUsageInputTokensDetails(cached_tokens=0), - output_tokens=0, - output_tokens_details=ResponseUsageOutputTokensDetails(reasoning_tokens=0), - total_tokens=0, - ) - deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - return deserialized - - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - - if polling: - polling_method: _UpdateMemoriesLROPollingMethod = _UpdateMemoriesLROPollingMethod( - lro_delay, - path_format_arguments=path_format_arguments, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, - **kwargs, - ) - else: - polling_method = cast(_UpdateMemoriesLROPollingMethod, NoPolling()) - - if cont_token: - return UpdateMemoriesLROPoller.from_continuation_token( - polling_method=polling_method, - continuation_token=cont_token, - client=self._client, - deserialization_callback=get_long_running_output, - ) - - return UpdateMemoriesLROPoller( - self._client, - raw_result, # type: ignore[possibly-undefined] - get_long_running_output, - polling_method, # pylint: disable=possibly-used-before-assignment - ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py deleted file mode 100644 index 158506d22235..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py +++ /dev/null @@ -1,69 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Optional, Iterable -from azure.core.exceptions import ResourceNotFoundError -from azure.core.tracing.decorator import distributed_trace -from ..models._models import Connection, ApiKeyCredentials -from ..models._enums import ConnectionType - - -class TelemetryOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`telemetry` attribute. - """ - - _connection_string: Optional[str] = None - - def __init__(self, outer_instance: "azure.ai.projects.AIProjectClient") -> None: # type: ignore[name-defined] - self._outer_instance = outer_instance - - @distributed_trace - def get_application_insights_connection_string(self) -> str: # pylint: disable=name-too-long - """Get the Application Insights connection string associated with the Project's Application Insights resource. - - :return: The Application Insights connection string if a the resource was enabled for the Project. - :rtype: str - :raises ~azure.core.exceptions.ResourceNotFoundError: An Application Insights connection does not - exist for this Foundry project. - """ - if not self._connection_string: - - # TODO: Two REST APIs calls can be replaced by one if we have had REST API for get_with_credentials(connection_type=ConnectionType.APPLICATION_INSIGHTS) - # Returns an empty Iterable if no connections exits. - connections: Iterable[Connection] = self._outer_instance.connections.list( - connection_type=ConnectionType.APPLICATION_INSIGHTS, - ) - - # Note: there can't be more than one AppInsights connection. - connection_name: Optional[str] = None - for connection in connections: - connection_name = connection.name - break - if not connection_name: - raise ResourceNotFoundError("No Application Insights connection found.") - - connection = self._outer_instance.connections._get_with_credentials( # pylint: disable=protected-access - name=connection_name - ) - - if isinstance(connection.credentials, ApiKeyCredentials): - if not connection.credentials.api_key: - raise ValueError("Application Insights connection does not have a connection string.") - self._connection_string = connection.credentials.api_key - else: - raise ValueError("Application Insights connection does not use API Key credentials.") - - return self._connection_string diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py deleted file mode 100644 index 4c2b14d84727..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# Code generated by Microsoft (R) Python Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is regenerated. -# -------------------------------------------------------------------------- - -from ._ai_project_instrumentor import AIProjectInstrumentor -from ._trace_function import trace_function - -__all__ = ["AIProjectInstrumentor", "trace_function"] diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py deleted file mode 100644 index 1a22ca314704..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py +++ /dev/null @@ -1,1535 +0,0 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks,docstring-missing-param,docstring-should-be-keyword -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -import copy -import functools -import importlib -import json -import logging -import os -from datetime import datetime -from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING -from urllib.parse import urlparse -from azure.ai.projects.models._models import Tool -from azure.core import CaseInsensitiveEnumMeta # type: ignore -from azure.core.settings import settings -from azure.core.tracing import AbstractSpan -from ._utils import ( - AGENTS_PROVIDER, - ERROR_TYPE, - GEN_AI_AGENT_DESCRIPTION, - GEN_AI_AGENT_ID, - GEN_AI_AGENT_NAME, - GEN_AI_EVENT_CONTENT, - GEN_AI_INPUT_MESSAGES, - GEN_AI_MESSAGE_ID, - GEN_AI_MESSAGE_STATUS, - GEN_AI_OPERATION_NAME, - GEN_AI_OUTPUT_MESSAGES, - GEN_AI_PROVIDER_NAME, - GEN_AI_SYSTEM_MESSAGE, - GEN_AI_SYSTEM_INSTRUCTION_EVENT, - GEN_AI_THREAD_ID, - GEN_AI_THREAD_RUN_ID, - GEN_AI_USAGE_INPUT_TOKENS, - GEN_AI_USAGE_OUTPUT_TOKENS, - GEN_AI_RUN_STEP_START_TIMESTAMP, - GEN_AI_RUN_STEP_END_TIMESTAMP, - GEN_AI_RUN_STEP_STATUS, - GEN_AI_AGENT_VERSION, - GEN_AI_AGENT_HOSTED_CPU, - GEN_AI_AGENT_HOSTED_MEMORY, - GEN_AI_AGENT_HOSTED_IMAGE, - GEN_AI_AGENT_HOSTED_PROTOCOL, - GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION, - AGENT_TYPE_PROMPT, - AGENT_TYPE_WORKFLOW, - AGENT_TYPE_HOSTED, - AGENT_TYPE_UNKNOWN, - GEN_AI_AGENT_TYPE, - ERROR_MESSAGE, - OperationName, - start_span, - _get_use_message_events, -) -from ._responses_instrumentor import _ResponsesInstrumentorPreview - -_Unset: Any = object() - -logger = logging.getLogger(__name__) - -try: - # pylint: disable = no-name-in-module - from opentelemetry.trace import Span, StatusCode - - _tracing_library_available = True -except ModuleNotFoundError: - _tracing_library_available = False - -if TYPE_CHECKING: - from .. import _types - -__all__ = [ - "AIProjectInstrumentor", -] - -_projects_traces_enabled: bool = False -_trace_agents_content: bool = False -_trace_context_propagation_enabled: bool = False -_trace_context_baggage_propagation_enabled: bool = False - - -def _inject_trace_context_sync(request): - """Synchronous event hook to inject trace context (traceparent) into outgoing requests. - - :param request: The httpx Request object. - :type request: httpx.Request - """ - try: - from opentelemetry import propagate - - carrier = dict(request.headers) - propagate.inject(carrier) - for key, value in carrier.items(): - key_lower = key.lower() - # Always include traceparent and tracestate - # Only include baggage if explicitly enabled - if key_lower in ("traceparent", "tracestate"): - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - elif key_lower == "baggage" and _trace_context_baggage_propagation_enabled: - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to inject trace context: %s", e) - - -async def _inject_trace_context_async(request): - """Async event hook to inject trace context (traceparent) into outgoing requests. - - :param request: The httpx Request object. - :type request: httpx.Request - """ - try: - from opentelemetry import propagate - - carrier = dict(request.headers) - propagate.inject(carrier) - for key, value in carrier.items(): - key_lower = key.lower() - # Always include traceparent and tracestate - # Only include baggage if explicitly enabled - if key_lower in ("traceparent", "tracestate"): - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - elif key_lower == "baggage" and _trace_context_baggage_propagation_enabled: - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to inject trace context: %s", e) - - -def _enable_trace_propagation_for_openai_client(openai_client): - """Enable trace context propagation for an OpenAI client. - - This function hooks into the httpx client used by the OpenAI SDK to inject - trace context headers (traceparent, tracestate) into outgoing HTTP requests. - This ensures that client-side spans and server-side spans share the same trace ID. - - :param openai_client: The OpenAI client instance. - :type openai_client: Any - """ - try: - # Access the underlying httpx client - if hasattr(openai_client, "_client"): - httpx_client = openai_client._client # pylint: disable=protected-access - - # Check if the client has event hooks support - if hasattr(httpx_client, "_event_hooks"): - event_hooks = httpx_client._event_hooks # pylint: disable=protected-access - - # Determine if this is an async client - is_async = hasattr(httpx_client, "__aenter__") - - # Add appropriate hook based on client type - if "request" in event_hooks: - hook_to_add = _inject_trace_context_async if is_async else _inject_trace_context_sync - - # Check if our hook is already registered to avoid duplicates - if hook_to_add not in event_hooks["request"]: - event_hooks["request"].append(hook_to_add) - logger.debug("Enabled trace propagation for %s OpenAI client", "async" if is_async else "sync") - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to enable trace propagation for OpenAI client: %s", e) - - -class TraceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): # pylint: disable=C4747 - """An enumeration class to represent different types of traces.""" - - AGENTS = "Agents" - PROJECT = "Project" - - -class AIProjectInstrumentor: - """ - A class for managing the trace instrumentation of the AIProjectClient. - - This class allows enabling or disabling tracing for AI Projects - and provides functionality to check whether instrumentation is active. - - .. warning:: - GenAI tracing is an experimental preview feature. To use this feature, you must set the - environment variable ``AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`` (case insensitive) - before calling the ``instrument()`` method. Spans, attributes, and events may be modified - in future versions. - - """ - - def __init__(self) -> None: - if not _tracing_library_available: - raise ModuleNotFoundError( - "Azure Core Tracing Opentelemetry is not installed. " - "Please install it using 'pip install azure-core-tracing-opentelemetry'" - ) - # We could support different semantic convention versions from the same library - # and have a parameter that specifies the version to use. - self._impl = _AIAgentsInstrumentorPreview() - self._responses_impl = _ResponsesInstrumentorPreview() - - def instrument( - self, - enable_content_recording: Optional[bool] = None, - enable_trace_context_propagation: Optional[bool] = None, - enable_baggage_propagation: Optional[bool] = None, - ) -> None: - """ - Enable trace instrumentation for AIProjectClient. - - .. warning:: - GenAI tracing is an experimental preview feature. To use this feature, you must set the - environment variable ``AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`` (case insensitive). - If this environment variable is not set or set to any value other than "true", - instrumentation will not be enabled and this method will return immediately without effect. - Spans, attributes, and events may be modified in future versions. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - :param enable_trace_context_propagation: Whether to enable automatic trace context propagation - to OpenAI SDK HTTP requests. When enabled, traceparent and tracestate headers will be injected - into requests made by OpenAI clients obtained via get_openai_client(), allowing server-side - spans to be correlated with client-side spans. `True` will enable it, `False` will - disable it. If no value is provided, then the value read from environment variable - AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION is used. If the environment - variable is not found, then the value will default to `True`. - Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after - the change; previously acquired clients are unaffected. - :type enable_trace_context_propagation: bool, optional - :param enable_baggage_propagation: Whether to include baggage headers in trace context propagation. - Only applies when enable_trace_context_propagation is True. `True` will enable baggage propagation, - `False` will disable it. If no value is provided, then the value read from environment variable - AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the environment - variable is not found, then the value will default to `False`. - Note: Baggage may contain sensitive application data. This value is evaluated dynamically on - each request, so changes apply immediately — but only for OpenAI clients that had the trace - context propagation hook registered at acquisition time (i.e. clients obtained via - get_openai_client() while enable_trace_context_propagation was True). Clients acquired when - trace context propagation was disabled will never propagate baggage regardless of this value. - :type enable_baggage_propagation: bool, optional - - """ - # Check experimental feature gate - experimental_enabled = os.environ.get("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", "false") - if experimental_enabled.lower() != "true": - logger.warning( - "GenAI tracing is not enabled. Set environment variable " - "AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true to enable this experimental feature." - ) - return - - self._impl.instrument(enable_content_recording, enable_trace_context_propagation, enable_baggage_propagation) - self._responses_impl.instrument(enable_content_recording) - - def uninstrument(self) -> None: - """ - Remove trace instrumentation for AIProjectClient. - - This method removes any active instrumentation, stopping the tracing - of AIProjectClient methods. - """ - self._impl.uninstrument() - self._responses_impl.uninstrument() - - def is_instrumented(self) -> bool: - """ - Check if trace instrumentation for AIProjectClient is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._impl.is_instrumented() - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content recording is enabled. - :rtype: bool - """ - return self._impl.is_content_recording_enabled() - - -class _AIAgentsInstrumentorPreview: - # pylint: disable=R0904 - """ - A class for managing the trace instrumentation of AI Agents. - - This class allows enabling or disabling tracing for AI Agents - and provides functionality to check whether instrumentation is active. - - .. note:: - This is a private implementation class. Use the public AIProjectInstrumentor class instead. - """ - - def _str_to_bool(self, s): - if s is None: - return False - return str(s).lower() == "true" - - def instrument( - self, - enable_content_recording: Optional[bool] = None, - enable_trace_context_propagation: Optional[bool] = None, - enable_baggage_propagation: Optional[bool] = None, - ): - """ - Enable trace instrumentation for AI Agents. - - .. note:: - This is a private implementation method. Use AIProjectInstrumentor.instrument() instead. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. - `True` will enable it, `False` will disable it. If no value is provided, then the - value read from environment variable AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION - is used. If the environment variable is not found, then the value will default to `True`. - Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after - the change; previously acquired clients are unaffected. - :type enable_trace_context_propagation: bool, optional - :param enable_baggage_propagation: Whether to include baggage in trace context propagation. - Only applies when enable_trace_context_propagation is True. `True` will enable it, `False` - will disable it. If no value is provided, then the value read from environment variable - AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the - environment variable is not found, then the value will default to `False`. - Note: This value is evaluated dynamically on each request, so changes apply immediately — - but only for OpenAI clients that had the trace context propagation hook registered at - acquisition time (i.e. clients obtained via get_openai_client() while - enable_trace_context_propagation was True). Clients acquired when trace context propagation - was disabled will never propagate baggage regardless of this value. - :type enable_baggage_propagation: bool, optional - - """ - if enable_content_recording is None: - - var_value = os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") - enable_content_recording = self._str_to_bool(var_value) - - if enable_trace_context_propagation is None: - var_value = os.environ.get("AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION") - enable_trace_context_propagation = True if var_value is None else self._str_to_bool(var_value) - - if enable_baggage_propagation is None: - var_value = os.environ.get("AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE") - enable_baggage_propagation = self._str_to_bool(var_value) - - if not self.is_instrumented(): - self._instrument_projects( - enable_content_recording, enable_trace_context_propagation, enable_baggage_propagation - ) - else: - self._set_enable_content_recording(enable_content_recording=enable_content_recording) - self._set_enable_trace_context_propagation( - enable_trace_context_propagation=enable_trace_context_propagation - ) - self._set_enable_baggage_propagation(enable_baggage_propagation=enable_baggage_propagation) - - def uninstrument(self): - """ - Disable trace instrumentation for AI Projects. - - This method removes any active instrumentation, stopping the tracing - of AI Projects. - """ - if self.is_instrumented(): - self._uninstrument_projects() - - def is_instrumented(self): - """ - Check if trace instrumentation for AI Projects is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._is_instrumented() - - def set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - """This function sets the content recording value. - - :param enable_content_recording: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_recording: bool - """ - self._set_enable_content_recording(enable_content_recording=enable_content_recording) - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content tracing is enabled. - :rtype bool - """ - return self._is_content_recording_enabled() - - def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None: - for attr in attrs: - key, value = attr - if value is not None: - span.add_attribute(key, value) - - def _parse_url(self, url): - parsed = urlparse(url) - server_address = parsed.hostname - port = parsed.port - return server_address, port - - def _remove_function_call_names_and_arguments(self, tool_calls: list) -> list: - tool_calls_copy = copy.deepcopy(tool_calls) - for tool_call in tool_calls_copy: - if "function" in tool_call: - if "name" in tool_call["function"]: - del tool_call["function"]["name"] - if "arguments" in tool_call["function"]: - del tool_call["function"]["arguments"] - if not tool_call["function"]: - del tool_call["function"] - return tool_calls_copy - - def _create_event_attributes( - self, - thread_id: Optional[str] = None, - agent_id: Optional[str] = None, - thread_run_id: Optional[str] = None, - message_id: Optional[str] = None, - message_status: Optional[str] = None, - run_step_status: Optional[str] = None, - created_at: Optional[datetime] = None, - completed_at: Optional[datetime] = None, - cancelled_at: Optional[datetime] = None, - failed_at: Optional[datetime] = None, - run_step_last_error: Optional[Any] = None, - usage: Optional[Any] = None, - ) -> Dict[str, Any]: - attrs: Dict[str, Any] = {GEN_AI_PROVIDER_NAME: AGENTS_PROVIDER} - if thread_id: - attrs[GEN_AI_THREAD_ID] = thread_id - - if agent_id: - attrs[GEN_AI_AGENT_ID] = agent_id - - if thread_run_id: - attrs[GEN_AI_THREAD_RUN_ID] = thread_run_id - - if message_id: - attrs[GEN_AI_MESSAGE_ID] = message_id - - if message_status: - attrs[GEN_AI_MESSAGE_STATUS] = self._status_to_string(message_status) - - if run_step_status: - attrs[GEN_AI_RUN_STEP_STATUS] = self._status_to_string(run_step_status) - - if created_at: - if isinstance(created_at, datetime): - attrs[GEN_AI_RUN_STEP_START_TIMESTAMP] = created_at.isoformat() - else: - # fallback in case integer or string gets passed - attrs[GEN_AI_RUN_STEP_START_TIMESTAMP] = str(created_at) - - end_timestamp = None - if completed_at: - end_timestamp = completed_at - elif cancelled_at: - end_timestamp = cancelled_at - elif failed_at: - end_timestamp = failed_at - - if isinstance(end_timestamp, datetime): - attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = end_timestamp.isoformat() - elif end_timestamp: - # fallback in case int or string gets passed - attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = str(end_timestamp) - - if run_step_last_error: - attrs[ERROR_MESSAGE] = run_step_last_error.message - attrs[ERROR_TYPE] = run_step_last_error.code - - if usage: - attrs[GEN_AI_USAGE_INPUT_TOKENS] = usage.prompt_tokens - attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.completion_tokens - - return attrs - - def add_thread_message_event( - self, - span, - message: Any, - usage: Optional[Any] = None, - ) -> None: - - content_body: Optional[Union[str, Dict[str, Any]]] = None - if _trace_agents_content: - # Handle processed dictionary messages - if isinstance(message, dict): - content = message.get("content") - if content: - content_body = content - - role = "unknown" - if isinstance(message, dict): - role = message.get("role", "unknown") - elif hasattr(message, "role"): - role = getattr(message, "role", "unknown") - - self._add_message_event( - span, - role, - content_body, - attachments=( - message.get("attachments") if isinstance(message, dict) else getattr(message, "attachments", None) - ), - thread_id=(message.get("thread_id") if isinstance(message, dict) else getattr(message, "thread_id", None)), - agent_id=(message.get("agent_id") if isinstance(message, dict) else getattr(message, "agent_id", None)), - message_id=(message.get("id") if isinstance(message, dict) else getattr(message, "id", None)), - thread_run_id=(message.get("run_id") if isinstance(message, dict) else getattr(message, "run_id", None)), - message_status=(message.get("status") if isinstance(message, dict) else getattr(message, "status", None)), - incomplete_details=( - message.get("incomplete_details") - if isinstance(message, dict) - else getattr(message, "incomplete_details", None) - ), - usage=usage, - ) - - def _add_message_event( # pylint: disable=too-many-branches,too-many-statements - self, - span, - role: str, - content: Optional[Union[str, dict[str, Any], List[dict[str, Any]]]] = None, - attachments: Any = None, - thread_id: Optional[str] = None, - agent_id: Optional[str] = None, - message_id: Optional[str] = None, - thread_run_id: Optional[str] = None, - message_status: Optional[str] = None, - incomplete_details: Optional[Any] = None, - usage: Optional[Any] = None, - ) -> None: - # TODO document new fields - - content_array: List[Dict[str, Any]] = [] - if _trace_agents_content: - if isinstance(content, List): - for block in content: - if isinstance(block, Dict): - if block.get("type") == "input_text" and "text" in block: - # Use optimized format with consistent "content" field - content_array.append({"type": "text", "content": block["text"]}) - break - elif content: - # Simple text content - content_array.append({"type": "text", "content": content}) - - if attachments: - # Add attachments as separate content items - for attachment in attachments: - attachment_body: Dict[str, Any] = { - "type": "attachment", - "content": {"id": attachment.file_id}, - } - if attachment.tools: - content_dict: Dict[str, Any] = attachment_body["content"] # type: ignore[assignment] - content_dict["tools"] = [self._get_field(tool, "type") for tool in attachment.tools] - content_array.append(attachment_body) - - # Add metadata fields if present - metadata: Dict[str, Any] = {} - if incomplete_details: - metadata["incomplete_details"] = incomplete_details - - # Combine content array with metadata if needed - event_data: Union[Dict[str, Any], List[Dict[str, Any]]] = {} - if metadata: - # When we have metadata, we need to wrap it differently - event_data = metadata - if content_array: - event_data["content"] = content_array - else: - # No metadata, use content array directly as the event data - event_data = content_array if isinstance(content_array, list) else {} - - use_events = _get_use_message_events() - - if use_events: - # Use events for message tracing - attributes = self._create_event_attributes( - thread_id=thread_id, - agent_id=agent_id, - thread_run_id=thread_run_id, - message_id=message_id, - message_status=message_status, - usage=usage, - ) - # Store as JSON - either array or object depending on metadata - if not metadata and content_array: - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - else: - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_data, ensure_ascii=False) - - event_name = None - if role == "user": - event_name = "gen_ai.input.message" - elif role == "system": - event_name = "gen_ai.system_instruction" - else: - event_name = "gen_ai.input.message" - - span.span_instance.add_event(name=event_name, attributes=attributes) - else: - # Use attributes for message tracing - # Prepare message content as JSON - message_json = json.dumps( - [{"role": role, "parts": content_array}] if content_array else [{"role": role}], ensure_ascii=False - ) - - # Determine which attribute to use based on role - if role == "user": - attribute_name = GEN_AI_INPUT_MESSAGES - elif role == "assistant": - attribute_name = GEN_AI_OUTPUT_MESSAGES - else: - # Default to input messages for other roles (including system) - attribute_name = GEN_AI_INPUT_MESSAGES - - # Set the attribute on the span - if span and span.span_instance.is_recording: - span.add_attribute(attribute_name, message_json) - - def _get_field(self, obj: Any, field: str) -> Any: - if not obj: - return None - - if isinstance(obj, dict): - return obj.get(field, None) - - return getattr(obj, field, None) - - def _add_instructions_event( - self, - span: "AbstractSpan", - instructions: Optional[str], - additional_instructions: Optional[str], - agent_id: Optional[str] = None, - thread_id: Optional[str] = None, - response_schema: Optional[Any] = None, - ) -> None: - # Early return if no instructions AND no response schema to trace - if not instructions and response_schema is None: - return - - content_array: List[Dict[str, Any]] = [] - - # Add instructions if provided - if instructions: - if _trace_agents_content: - # Combine instructions if both exist - if additional_instructions: - combined_text = f"{instructions} {additional_instructions}" - else: - combined_text = instructions - - # Use optimized format with consistent "content" field - content_array.append({"type": "text", "content": combined_text}) - else: - # Content recording disabled, but indicate that text instructions exist - content_array.append({"type": "text"}) - - # Add response schema if provided - if response_schema is not None: - if _trace_agents_content: - # Convert schema to JSON string if it's a dict/object - if isinstance(response_schema, dict): - schema_str = json.dumps(response_schema, ensure_ascii=False) - elif hasattr(response_schema, "as_dict"): - # Handle model objects that have as_dict() method (e.g., ResponseFormatJsonSchemaSchema) - schema_dict = response_schema.as_dict() - schema_str = json.dumps(schema_dict, ensure_ascii=False) - # TODO: is this 'elif' still needed, not that we added the above? - elif hasattr(response_schema, "__dict__"): - # Handle model objects by converting to dict first - schema_dict = {k: v for k, v in response_schema.__dict__.items() if not k.startswith("_")} - schema_str = json.dumps(schema_dict, ensure_ascii=False) - else: - schema_str = str(response_schema) - - content_array.append({"type": "response_schema", "content": schema_str}) - else: - # Content recording disabled, but indicate that response schema exists - content_array.append({"type": "response_schema"}) - - use_events = _get_use_message_events() - - if use_events: - # Use events for instructions tracing - attributes = self._create_event_attributes(agent_id=agent_id, thread_id=thread_id) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - span.span_instance.add_event(name=GEN_AI_SYSTEM_INSTRUCTION_EVENT, attributes=attributes) - else: - # Use attributes for instructions tracing - # System instructions format: array of content objects without role/parts wrapper - message_json = json.dumps(content_array, ensure_ascii=False) - if span and span.span_instance.is_recording: - span.add_attribute(GEN_AI_SYSTEM_MESSAGE, message_json) - - def _status_to_string(self, status: Any) -> str: - return status.value if hasattr(status, "value") else status - - @staticmethod - def agent_api_response_to_str(response_format: Any) -> Optional[str]: - """ - Convert response_format to string. - - :param response_format: The response format. - :type response_format: Any - :returns: string for the response_format. - :rtype: Optional[str] - :raises: Value error if response_format is not a supported type. - """ - if isinstance(response_format, str) or response_format is None: - return response_format - raise ValueError(f"Unknown response format {type(response_format)}") - - def start_create_agent_span( # pylint: disable=too-many-locals - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - model: Optional[str] = None, - name: Optional[str] = None, - description: Optional[str] = None, - instructions: Optional[str] = None, - _tools: Optional[List[Tool]] = None, - _tool_resources: Optional[Any] = None, # TODO: Used to be: _tool_resources: Optional[ItemResource] = None, - # _toolset: Optional["ToolSet"] = None, - temperature: Optional[float] = None, - top_p: Optional[float] = None, - response_format: Optional[Any] = None, - reasoning_effort: Optional[str] = None, - reasoning_summary: Optional[str] = None, - text: Optional[Any] = None, # pylint: disable=unused-argument - structured_inputs: Optional[Any] = None, - agent_type: Optional[str] = None, - workflow_yaml: Optional[str] = None, - hosted_cpu: Optional[str] = None, - hosted_memory: Optional[str] = None, - hosted_image: Optional[str] = None, - hosted_protocol: Optional[str] = None, - hosted_protocol_version: Optional[str] = None, - ) -> "Optional[AbstractSpan]": - span = start_span( - OperationName.CREATE_AGENT, - server_address=server_address, - port=port, - span_name=f"{OperationName.CREATE_AGENT.value} {name}", - model=model, - temperature=temperature, - top_p=top_p, - response_format=_AIAgentsInstrumentorPreview.agent_api_response_to_str(response_format), - reasoning_effort=reasoning_effort, - reasoning_summary=reasoning_summary, - structured_inputs=(str(structured_inputs) if structured_inputs is not None else None), - ) - if span and span.span_instance.is_recording: - span.add_attribute(GEN_AI_OPERATION_NAME, OperationName.CREATE_AGENT.value) - if name: - span.add_attribute(GEN_AI_AGENT_NAME, name) - if description: - span.add_attribute(GEN_AI_AGENT_DESCRIPTION, description) - if agent_type: - span.add_attribute(GEN_AI_AGENT_TYPE, agent_type) - - # Add hosted agent specific attributes - if hosted_cpu: - span.add_attribute(GEN_AI_AGENT_HOSTED_CPU, hosted_cpu) - if hosted_memory: - span.add_attribute(GEN_AI_AGENT_HOSTED_MEMORY, hosted_memory) - if hosted_image: - span.add_attribute(GEN_AI_AGENT_HOSTED_IMAGE, hosted_image) - if hosted_protocol: - span.add_attribute(GEN_AI_AGENT_HOSTED_PROTOCOL, hosted_protocol) - if hosted_protocol_version: - span.add_attribute(GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION, hosted_protocol_version) - - # Extract response schema from text parameter if available - response_schema = None - if response_format and text: - # Extract schema from text.format.schema if available - if hasattr(text, "format"): - format_info = getattr(text, "format", None) - if format_info and hasattr(format_info, "schema"): - response_schema = getattr(format_info, "schema", None) - elif isinstance(text, dict): - format_info = text.get("format") - if format_info and isinstance(format_info, dict): - response_schema = format_info.get("schema") - - # Add instructions event (if instructions exist) - self._add_instructions_event(span, instructions, None, response_schema=response_schema) - - # Add workflow event if workflow type agent (always add event, but only include YAML content if content recording enabled) - if workflow_yaml is not None: - # Always create event with empty array or workflow content based on recording setting - if _trace_agents_content: - # Include actual workflow YAML when content recording is enabled - event_body: List[Dict[str, Any]] = [{"type": "workflow", "content": workflow_yaml}] - else: - # Empty array when content recording is disabled (agent type already indicates it's a workflow) - event_body = [] - attributes = self._create_event_attributes() - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False) - span.span_instance.add_event(name="gen_ai.agent.workflow", attributes=attributes) - - return span - - def start_create_thread_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - messages: Optional[List[Dict[str, str]]] = None, - # _tool_resources: Optional["ToolResources"] = None, - ) -> "Optional[AbstractSpan]": - span = start_span(OperationName.CREATE_THREAD, server_address=server_address, port=port) - if span and span.span_instance.is_recording: - for message in messages or []: - self.add_thread_message_event(span, message) - - return span - - def get_server_address_from_arg(self, arg: Any) -> Optional[Tuple[str, Optional[int]]]: - """ - Extracts the base endpoint and port from the provided arguments _config.endpoint attribute, if that exists. - - :param arg: The argument from which the server address is to be extracted. - :type arg: Any - :return: A tuple of (base endpoint, port) or None if endpoint is not found. - :rtype: Optional[Tuple[str, Optional[int]]] - """ - if hasattr(arg, "_config") and hasattr( - arg._config, # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - "endpoint", - ): - endpoint = ( - arg._config.endpoint # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - ) - parsed_url = urlparse(endpoint) - return f"{parsed_url.scheme}://{parsed_url.netloc}", parsed_url.port - return None - - def _create_agent_span_from_parameters( - self, *args, **kwargs - ): # pylint: disable=too-many-statements,too-many-locals,too-many-branches,docstring-missing-param - """Extract parameters and create span for create_agent tracing.""" - server_address_info = self.get_server_address_from_arg(args[0]) - server_address = server_address_info[0] if server_address_info else None - port = server_address_info[1] if server_address_info else None - - # Extract parameters from the new nested structure - agent_name = kwargs.get("agent_name") - definition = kwargs.get("definition", {}) - if definition is None: - body = kwargs.get("body", {}) - definition = body.get("definition", {}) - - # Extract parameters from definition - model = definition.get("model") - instructions = definition.get("instructions") - temperature = definition.get("temperature") - top_p = definition.get("top_p") - tools = definition.get("tools") - reasoning = definition.get("reasoning") - text = definition.get("text") - structured_inputs = None - description = definition.get("description") - tool_resources = definition.get("tool_resources") - workflow_yaml = definition.get("workflow") # Extract workflow YAML for workflow agents - # toolset = definition.get("toolset") - - # Extract hosted agent specific attributes - hosted_cpu = definition.get("cpu") - hosted_memory = definition.get("memory") - hosted_image = definition.get("image") - hosted_protocol = None - hosted_protocol_version = None - container_protocol_versions = definition.get("container_protocol_versions") - if container_protocol_versions and len(container_protocol_versions) > 0: - # Extract protocol and version from first entry - protocol_record = container_protocol_versions[0] - if hasattr(protocol_record, "protocol"): - hosted_protocol = getattr(protocol_record, "protocol", None) - elif isinstance(protocol_record, dict): - hosted_protocol = protocol_record.get("protocol") - - if hasattr(protocol_record, "version"): - hosted_protocol_version = getattr(protocol_record, "version", None) - elif isinstance(protocol_record, dict): - hosted_protocol_version = protocol_record.get("version") - - # Determine agent type from definition - # Check for hosted agent first (most specific - has container/image configuration) - agent_type = None - if hosted_image or hosted_cpu or hosted_memory: - agent_type = AGENT_TYPE_HOSTED - elif workflow_yaml: - agent_type = AGENT_TYPE_WORKFLOW - elif instructions or model: - # Prompt agent - identified by having instructions and/or a model. - # Note: An agent with only a model (no instructions) is treated as a prompt agent, - # though this is uncommon. Typically prompt agents have both model and instructions. - agent_type = AGENT_TYPE_PROMPT - else: - # Unknown type - set to "unknown" to indicate we couldn't determine it - agent_type = AGENT_TYPE_UNKNOWN - - # Extract reasoning effort and summary from reasoning if available - reasoning_effort = None - reasoning_summary = None - if reasoning: - # Handle different types of reasoning objects - if hasattr(reasoning, "effort") and hasattr(reasoning, "summary"): - # Azure OpenAI Reasoning model object - reasoning_effort = getattr(reasoning, "effort", None) - reasoning_summary = getattr(reasoning, "summary", None) - elif isinstance(reasoning, dict): - # Dictionary format - reasoning_effort = reasoning.get("effort") - reasoning_summary = reasoning.get("summary") - elif isinstance(reasoning, str): - # Try to parse as JSON if it's a string - try: - reasoning_dict = json.loads(reasoning) - if isinstance(reasoning_dict, dict): - reasoning_effort = reasoning_dict.get("effort") - reasoning_summary = reasoning_dict.get("summary") - except (json.JSONDecodeError, ValueError): - # If parsing fails, treat the whole string as effort - reasoning_effort = reasoning - - # Extract response format from text.format if available - response_format = None - if text: - # Handle different types of text objects - if hasattr(text, "format"): - # Azure AI Agents PromptAgentDefinitionTextOptions model object - format_info = getattr(text, "format", None) - if format_info: - if hasattr(format_info, "type"): - # Format is also a model object - response_format = getattr(format_info, "type", None) - elif isinstance(format_info, dict): - # Format is a dictionary - response_format = format_info.get("type") - elif isinstance(text, dict): - # Dictionary format - format_info = text.get("format") - if format_info and isinstance(format_info, dict): - format_type = format_info.get("type") - if format_type: - response_format = format_type - elif isinstance(text, str): - # Try to parse as JSON if it's a string - try: - text_dict = json.loads(text) - if isinstance(text_dict, dict): - format_info = text_dict.get("format") - if format_info and isinstance(format_info, dict): - format_type = format_info.get("type") - if format_type: - response_format = format_type - except (json.JSONDecodeError, ValueError): - # If parsing fails, ignore - pass - - # Create and return the span - return self.start_create_agent_span( - server_address=server_address, - port=port, - name=agent_name, - model=model, - description=description, - instructions=instructions, - _tools=tools, - _tool_resources=tool_resources, - temperature=temperature, - top_p=top_p, - response_format=response_format, - reasoning_effort=reasoning_effort, - reasoning_summary=reasoning_summary, - text=text, - structured_inputs=structured_inputs, - agent_type=agent_type, - workflow_yaml=workflow_yaml, - hosted_cpu=hosted_cpu, - hosted_memory=hosted_memory, - hosted_image=hosted_image, - hosted_protocol=hosted_protocol, - hosted_protocol_version=hosted_protocol_version, - ) - - def trace_create_agent(self, function, *args, **kwargs): - span = self._create_agent_span_from_parameters(*args, **kwargs) - - if span is None: - return function(*args, **kwargs) - - with span: - try: - result = function(*args, **kwargs) - span.add_attribute(GEN_AI_AGENT_ID, result.id) - span.add_attribute(GEN_AI_AGENT_VERSION, result.version) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - async def trace_create_agent_async(self, function, *args, **kwargs): - span = self._create_agent_span_from_parameters(*args, **kwargs) - - if span is None: - return await function(*args, **kwargs) - - with span: - try: - result = await function(*args, **kwargs) - span.add_attribute(GEN_AI_AGENT_ID, result.id) - span.add_attribute(GEN_AI_AGENT_VERSION, result.version) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - def _create_thread_span_from_parameters(self, *args, **kwargs): - """Extract parameters, process messages, and create span for create_thread tracing.""" - server_address_info = self.get_server_address_from_arg(args[0]) - server_address = server_address_info[0] if server_address_info else None - port = server_address_info[1] if server_address_info else None - messages = kwargs.get("messages") - items = kwargs.get("items") - if items is None: - body = kwargs.get("body") - if isinstance(body, dict): - items = body.get("items") - - # Process items if available to extract content from generators - processed_messages = messages - if items: - processed_messages = [] - for item in items: - # Handle model objects like ResponsesUserMessageItemParam, ResponsesSystemMessageItemParam - if hasattr(item, "__dict__"): - final_content = str(getattr(item, "content", "")) - # Create message structure for telemetry - role = getattr(item, "role", "unknown") - processed_messages.append({"role": role, "content": final_content}) - else: - # Handle dict items or simple string items - if isinstance(item, dict): - processed_messages.append(item) - else: - # Handle simple string items - processed_messages.append({"role": "unknown", "content": str(item)}) - - # Create and return the span - return self.start_create_thread_span(server_address=server_address, port=port, messages=processed_messages) - - def trace_create_thread(self, function, *args, **kwargs): - span = self._create_thread_span_from_parameters(*args, **kwargs) - - if span is None: - return function(*args, **kwargs) - - with span: - try: - result = function(*args, **kwargs) - span.add_attribute(GEN_AI_THREAD_ID, result.get("id")) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - async def trace_create_thread_async(self, function, *args, **kwargs): - span = self._create_thread_span_from_parameters(*args, **kwargs) - - if span is None: - return await function(*args, **kwargs) - - with span: - try: - result = await function(*args, **kwargs) - span.add_attribute(GEN_AI_THREAD_ID, result.get("id")) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - def trace_list_messages_async(self, function, *args, **kwargs): - """Placeholder method for list messages async tracing. - - The full instrumentation infrastructure for list operations - is not yet implemented, so we simply call the original function. - - :param function: The original function to be called. - :type function: Callable - :param args: Positional arguments passed to the original function. - :type args: tuple - :param kwargs: Keyword arguments passed to the original function. - :type kwargs: dict - :return: The result of calling the original function. - :rtype: Any - """ - return function(*args, **kwargs) - - def trace_list_run_steps_async(self, function, *args, **kwargs): - """Placeholder method for list run steps async tracing. - - The full instrumentation infrastructure for list operations - is not yet implemented, so we simply call the original function. - - :param function: The original function to be called. - :type function: Callable - :param args: Positional arguments passed to the original function. - :type args: tuple - :param kwargs: Keyword arguments passed to the original function. - :type kwargs: dict - :return: The result of calling the original function. - :rtype: Any - """ - return function(*args, **kwargs) - - def _trace_sync_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.AGENTS, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to a synchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - def inner(*args, **kwargs): # pylint: disable=R0911 - span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 - if span_impl_type is None: - return function(*args, **kwargs) - - class_function_name = function.__qualname__ - - if class_function_name.endswith(".create_version") and ("AgentsOperations" in class_function_name): - kwargs.setdefault("merge_span", True) - return self.trace_create_agent(function, *args, **kwargs) - # if class_function_name.startswith("ConversationsOperations.create"): - # kwargs.setdefault("merge_span", True) - # return self.trace_create_thread(function, *args, **kwargs) - return function(*args, **kwargs) # Ensure all paths return - - return inner - - def _trace_async_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.AGENTS, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to an asynchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - async def inner(*args, **kwargs): # pylint: disable=R0911 - span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 - if span_impl_type is None: - return await function(*args, **kwargs) - - class_function_name = function.__qualname__ - - if class_function_name.endswith(".create_version") and ("AgentsOperations" in class_function_name): - kwargs.setdefault("merge_span", True) - return await self.trace_create_agent_async(function, *args, **kwargs) - # if class_function_name.startswith("ConversationOperations.create"): - # kwargs.setdefault("merge_span", True) - # return await self.trace_create_thread_async(function, *args, **kwargs) - return await function(*args, **kwargs) # Ensure all paths return - - return inner - - def _trace_async_list_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.AGENTS, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to an asynchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. - Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - def inner(*args, **kwargs): # pylint: disable=R0911 - span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 - if span_impl_type is None: - return function(*args, **kwargs) - - class_function_name = function.__qualname__ - if class_function_name.startswith("MessagesOperations.list"): - kwargs.setdefault("merge_span", True) - return self.trace_list_messages_async(function, *args, **kwargs) - if class_function_name.startswith("RunStepsOperations.list"): - kwargs.setdefault("merge_span", True) - return self.trace_list_run_steps_async(function, *args, **kwargs) - # Handle the default case (if the function name does not match) - return None # Ensure all paths return - - return inner - - def _inject_async(self, f, _trace_type, _name): - if _name.startswith("list"): - wrapper_fun = self._trace_async_list_function(f) - else: - wrapper_fun = self._trace_async_function(f) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _inject_sync(self, f, _trace_type, _name): - wrapper_fun = self._trace_sync_function(f) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _agents_apis(self): - sync_apis = ( - ( - "azure.ai.projects.operations", - "AgentsOperations", - "create_version", - TraceType.AGENTS, - "create_version", - ), - # ( - # "azure.ai.agents.operations", - # "ConversationsOperations", - # "create", - # TraceType.AGENTS, - # "create", - # ), - ) - async_apis = ( - ( - "azure.ai.projects.aio.operations", - "AgentsOperations", - "create_version", - TraceType.AGENTS, - "create_version", - ), - # ( - # "azure.ai.agents.aio.operations", - # "ConversationsOperations", - # "create", - # TraceType.AGENTS, - # "create", - # ), - ) - return sync_apis, async_apis - - def _project_apis(self): - """Define AIProjectClient APIs to instrument for trace propagation. - - :return: A tuple containing sync and async API tuples. - :rtype: Tuple[Tuple, Tuple] - """ - sync_apis = ( - ( - "azure.ai.projects", - "AIProjectClient", - "get_openai_client", - TraceType.PROJECT, - "get_openai_client", - ), - ) - async_apis = ( - ( - "azure.ai.projects.aio", - "AIProjectClient", - "get_openai_client", - TraceType.PROJECT, - "get_openai_client", - ), - ) - return sync_apis, async_apis - - def _inject_openai_client(self, f, _trace_type, _name): - """Injector for get_openai_client that enables trace context propagation if opted in. - - :return: The wrapped function with trace context propagation enabled. - :rtype: Callable - """ - - @functools.wraps(f) - def wrapper(*args, **kwargs): - openai_client = f(*args, **kwargs) - if _trace_context_propagation_enabled: - _enable_trace_propagation_for_openai_client(openai_client) - return openai_client - - wrapper._original = f # type: ignore # pylint: disable=protected-access - return wrapper - - def _agents_api_list(self): - sync_apis, async_apis = self._agents_apis() - yield sync_apis, self._inject_sync - yield async_apis, self._inject_async - - def _project_api_list(self): - """Generate project API list with custom injector. - - :return: A generator yielding API tuples with injectors. - :rtype: Generator - """ - sync_apis, async_apis = self._project_apis() - yield sync_apis, self._inject_openai_client - yield async_apis, self._inject_openai_client - - def _generate_api_and_injector(self, apis): - for api, injector in apis: - for module_name, class_name, method_name, trace_type, name in api: - try: - module = importlib.import_module(module_name) - api = getattr(module, class_name) - if hasattr(api, method_name): - # The function list is sync in both sync and async classes. - yield api, method_name, trace_type, injector, name - except AttributeError as e: - # Log the attribute exception with the missing class information - logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "AttributeError: The module '%s' does not have the class '%s'. %s", - module_name, - class_name, - str(e), - ) - except Exception as e: # pylint: disable=broad-except - # Log other exceptions as a warning, as we are not sure what they might be - logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "An unexpected error occurred: '%s'", str(e) - ) - - def _available_projects_apis_and_injectors(self): - """ - Generates a sequence of tuples containing Agents and Project API classes, method names, and - corresponding injector functions. - - :return: A generator yielding tuples. - :rtype: tuple - """ - yield from self._generate_api_and_injector(self._agents_api_list()) - yield from self._generate_api_and_injector(self._project_api_list()) - - def _instrument_projects( - self, - enable_content_tracing: bool = False, - enable_trace_context_propagation: bool = False, - enable_baggage_propagation: bool = False, - ): - """This function modifies the methods of the Projects API classes to - inject logic before calling the original methods. - The original methods are stored as _original attributes of the methods. - - :param enable_content_tracing: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_tracing: bool - :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. - :type enable_trace_context_propagation: bool - :param enable_baggage_propagation: Whether to include baggage in trace context propagation. - :type enable_baggage_propagation: bool - """ - # pylint: disable=W0603 - global _projects_traces_enabled - global _trace_agents_content - global _trace_context_propagation_enabled - global _trace_context_baggage_propagation_enabled - if _projects_traces_enabled: - raise RuntimeError("Traces already started for AI Agents") - - _projects_traces_enabled = True - _trace_agents_content = enable_content_tracing - _trace_context_propagation_enabled = enable_trace_context_propagation - _trace_context_baggage_propagation_enabled = enable_baggage_propagation - for ( - api, - method, - trace_type, - injector, - name, - ) in self._available_projects_apis_and_injectors(): - # Check if the method of the api class has already been modified - if not hasattr(getattr(api, method), "_original"): - setattr(api, method, injector(getattr(api, method), trace_type, name)) - - def _uninstrument_projects(self): - """This function restores the original methods of the Projects API classes - by assigning them back from the _original attributes of the modified methods. - """ - # pylint: disable=W0603 - global _projects_traces_enabled - global _trace_agents_content - global _trace_context_propagation_enabled - global _trace_context_baggage_propagation_enabled - _trace_agents_content = False - _trace_context_propagation_enabled = False - _trace_context_baggage_propagation_enabled = False - for api, method, _, _, _ in self._available_projects_apis_and_injectors(): - if hasattr(getattr(api, method), "_original"): - setattr(api, method, getattr(getattr(api, method), "_original")) - - _projects_traces_enabled = False - - def _is_instrumented(self): - """This function returns True if Projects API has already been instrumented - for tracing and False if it has not been instrumented. - - :return: A value indicating whether the Projects API is currently instrumented or not. - :rtype: bool - """ - return _projects_traces_enabled - - def _set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - """This function sets the content recording value. - - :param enable_content_recording: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_recording: bool - """ - global _trace_agents_content # pylint: disable=W0603 - _trace_agents_content = enable_content_recording - - def _is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content tracing is enabled. - :rtype bool - """ - return _trace_agents_content - - def _set_enable_trace_context_propagation(self, enable_trace_context_propagation: bool = False) -> None: - """This function sets the trace context propagation value. - - :param enable_trace_context_propagation: Indicates whether automatic trace context propagation should be enabled. - :type enable_trace_context_propagation: bool - """ - global _trace_context_propagation_enabled # pylint: disable=W0603 - _trace_context_propagation_enabled = enable_trace_context_propagation - - def _set_enable_baggage_propagation(self, enable_baggage_propagation: bool = False) -> None: - """This function sets the baggage propagation value. - - :param enable_baggage_propagation: Indicates whether baggage should be included in trace context propagation. - :type enable_baggage_propagation: bool - """ - global _trace_context_baggage_propagation_enabled # pylint: disable=W0603 - _trace_context_baggage_propagation_enabled = enable_baggage_propagation - - def record_error(self, span, exc): - # Set the span status to error - if isinstance(span.span_instance, Span): # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status( - StatusCode.ERROR, # pyright: ignore [reportPossiblyUnboundVariable] - description=str(exc), - ) - module = getattr(exc, "__module__", "") - module = module if module != "builtins" else "" - error_type = f"{module}.{type(exc).__name__}" if module else type(exc).__name__ - self._set_attributes(span, ("error.type", error_type)) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py deleted file mode 100644 index 95cb28183b35..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py +++ /dev/null @@ -1,4883 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression,too-many-lines -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -# pyright: reportPossiblyUnboundVariable=false -# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks,docstring-missing-param,docstring-should-be-keyword,docstring-missing-return,docstring-missing-rtype,broad-exception-caught,logging-fstring-interpolation,unused-variable,unused-argument,protected-access,global-variable-not-assigned,global-statement -# Pylint disables are appropriate for this internal instrumentation class because: -# - Extensive documentation isn't required for internal methods (docstring-missing-*) -# - Broad exception catching is often necessary for telemetry (shouldn't break user code) -# - Protected access is needed for instrumentation to hook into client internals -# - Some unused variables/arguments exist for API compatibility and future extensibility -# - Global variables are used for metrics state management across instances -# - Line length and complexity limits are relaxed for instrumentation code -import functools -import json -import logging -import os -import time -from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING -from urllib.parse import urlparse -from azure.core import CaseInsensitiveEnumMeta # type: ignore -from azure.core.tracing import AbstractSpan -from ._utils import ( - ERROR_TYPE, - GEN_AI_AGENT_ID, - GEN_AI_AGENT_NAME, - GEN_AI_ASSISTANT_MESSAGE_EVENT, - GEN_AI_CLIENT_OPERATION_DURATION, - GEN_AI_CLIENT_TOKEN_USAGE, - GEN_AI_CONVERSATION_ID, - GEN_AI_CONVERSATION_ITEM_EVENT, - GEN_AI_CONVERSATION_ITEM_ID, - GEN_AI_EVENT_CONTENT, - GEN_AI_INPUT_MESSAGES, - GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, - GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT, - GEN_AI_OPERATION_NAME, - GEN_AI_OUTPUT_MESSAGES, - GEN_AI_PROVIDER_NAME, - GEN_AI_REQUEST_MODEL, - GEN_AI_REQUEST_TOOLS, - GEN_AI_RESPONSE_FINISH_REASONS, - GEN_AI_RESPONSE_ID, - GEN_AI_RESPONSE_MODEL, - GEN_AI_TOKEN_TYPE, - GEN_AI_TOOL_MESSAGE_EVENT, - GEN_AI_USAGE_INPUT_TOKENS, - GEN_AI_USAGE_OUTPUT_TOKENS, - GEN_AI_USER_MESSAGE_EVENT, - GEN_AI_WORKFLOW_ACTION_EVENT, - OPERATION_NAME_CHAT, - OPERATION_NAME_INVOKE_AGENT, - OperationName, - SERVER_ADDRESS, - SERVER_PORT, - SPAN_NAME_CHAT, - SPAN_NAME_INVOKE_AGENT, - _get_use_message_events, - _get_use_simple_tool_format, - start_span, - RESPONSES_PROVIDER, -) - -_Unset: Any = object() - -logger = logging.getLogger(__name__) - -try: # pylint: disable=unused-import - # pylint: disable = no-name-in-module - from opentelemetry.trace import StatusCode - from opentelemetry.metrics import get_meter - - _tracing_library_available = True -except ModuleNotFoundError: - _tracing_library_available = False - -if TYPE_CHECKING: - pass - -__all__ = [ - "ResponsesInstrumentor", -] - -_responses_traces_enabled: bool = False -_trace_responses_content: bool = False -_trace_binary_data: bool = False - -# Metrics instruments -_operation_duration_histogram = None -_token_usage_histogram = None - - -class TraceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): # pylint: disable=C4747 - """An enumeration class to represent different types of traces.""" - - RESPONSES = "Responses" - CONVERSATIONS = "Conversations" - - -class ResponsesInstrumentor: - """ - A class for managing the trace instrumentation of OpenAI Responses and Conversations APIs. - - This class allows enabling or disabling tracing for OpenAI Responses and Conversations API calls - and provides functionality to check whether instrumentation is active. - """ - - def __init__(self) -> None: - if not _tracing_library_available: - logger.warning( - "OpenTelemetry is not available. " - "Please install opentelemetry-api and opentelemetry-sdk to enable tracing." - ) - # We could support different semantic convention versions from the same library - # and have a parameter that specifies the version to use. - self._impl = _ResponsesInstrumentorPreview() - - def instrument(self, enable_content_recording: Optional[bool] = None) -> None: - """ - Enable trace instrumentation for OpenAI Responses and Conversations APIs. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - """ - self._impl.instrument(enable_content_recording) - - def uninstrument(self) -> None: - """ - Remove trace instrumentation for OpenAI Responses and Conversations APIs. - - This method removes any active instrumentation, stopping the tracing - of OpenAI Responses and Conversations API methods. - """ - self._impl.uninstrument() - - def is_instrumented(self) -> bool: - """ - Check if trace instrumentation for OpenAI Responses and Conversations APIs is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._impl.is_instrumented() - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content recording is enabled. - :rtype: bool - """ - return self._impl.is_content_recording_enabled() - - def is_binary_data_enabled(self) -> bool: - """This function gets the binary data tracing value. - - :return: A bool value indicating whether binary data tracing is enabled. - :rtype: bool - """ - return self._impl.is_binary_data_enabled() - - def is_simple_tool_format_enabled(self) -> bool: - """This function gets the simple tool format value. - - When enabled, function tool calls use a simplified OTEL-compliant format: - tool_call: {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} - tool_call_response: {"type": "tool_call_response", "id": "...", "result": "..."} - - :return: A bool value indicating whether simple tool format is enabled. - :rtype: bool - """ - return _get_use_simple_tool_format() - - -class _ResponsesInstrumentorPreview: # pylint: disable=too-many-instance-attributes,too-many-statements,too-many-public-methods - """ - A class for managing the trace instrumentation of OpenAI Responses API. - - This class allows enabling or disabling tracing for OpenAI Responses API calls - and provides functionality to check whether instrumentation is active. - """ - - def _str_to_bool(self, s): - if s is None: - return False - return str(s).lower() == "true" - - def _is_instrumentation_enabled(self) -> bool: - """Check if instrumentation is enabled via environment variable. - - Returns True if AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API is not set or is "true" (case insensitive). - Returns False if the environment variable is set to any other value. - """ - env_value = os.environ.get("AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API") - if env_value is None: - return True # Default to enabled if not specified - return str(env_value).lower() == "true" - - def _initialize_metrics(self): - """Initialize OpenTelemetry metrics instruments.""" - global _operation_duration_histogram, _token_usage_histogram # pylint: disable=global-statement - - if not _tracing_library_available: - return - - try: - meter = get_meter(__name__) # pyright: ignore [reportPossiblyUnboundVariable] - - # Operation duration histogram - _operation_duration_histogram = meter.create_histogram( - name=GEN_AI_CLIENT_OPERATION_DURATION, - description="Duration of GenAI operations", - unit="s", - ) - - # Token usage histogram - _token_usage_histogram = meter.create_histogram( - name=GEN_AI_CLIENT_TOKEN_USAGE, - description="Token usage for GenAI operations", - unit="token", - ) - - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to initialize metrics: %s", e) - - def _record_operation_duration( - self, - duration: float, - operation_name: str, - server_address: Optional[str] = None, - port: Optional[int] = None, - model: Optional[str] = None, - error_type: Optional[str] = None, - ): - """Record operation duration metrics.""" - global _operation_duration_histogram # pylint: disable=global-variable-not-assigned - - if not _operation_duration_histogram: - return - - attributes = { - GEN_AI_OPERATION_NAME: operation_name, - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - } - - if server_address: - attributes[SERVER_ADDRESS] = server_address - if port: - attributes[SERVER_PORT] = str(port) - if model: - attributes[GEN_AI_REQUEST_MODEL] = model - if error_type: - attributes[ERROR_TYPE] = error_type - - try: - _operation_duration_histogram.record(duration, attributes) - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to record operation duration: %s", e) - - def _record_token_usage( - self, - token_count: int, - token_type: str, - operation_name: str, - server_address: Optional[str] = None, - model: Optional[str] = None, - ): - """Record token usage metrics.""" - global _token_usage_histogram # pylint: disable=global-variable-not-assigned - - if not _token_usage_histogram: - return - - attributes = { - GEN_AI_OPERATION_NAME: operation_name, - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - GEN_AI_TOKEN_TYPE: token_type, - } - - if server_address: - attributes[SERVER_ADDRESS] = server_address - if model: - attributes[GEN_AI_REQUEST_MODEL] = model - - try: - _token_usage_histogram.record(token_count, attributes) - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to record token usage: %s", e) - - def _record_token_metrics_from_response( - self, - response: Any, - operation_name: str, - server_address: Optional[str] = None, - model: Optional[str] = None, - ): - """Extract and record token usage metrics from response.""" - try: - if hasattr(response, "usage"): - usage = response.usage - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self._record_token_usage( - usage.prompt_tokens, - "input", - operation_name, - server_address, - model, - ) - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self._record_token_usage( - usage.completion_tokens, - "completion", - operation_name, - server_address, - model, - ) - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to extract token metrics from response: %s", e) - - def _record_metrics( # pylint: disable=docstring-missing-type - self, - operation_type: str, - duration: float, - result: Any = None, - span_attributes: Optional[Dict[str, Any]] = None, - error_type: Optional[str] = None, - ): - """ - Record comprehensive metrics for different API operation types. - - :param operation_type: Type of operation ("responses", "conversation", "conversation_items") - :param duration: Operation duration in seconds - :param result: API response object for extracting response-specific attributes - :param span_attributes: Dictionary of span attributes to extract relevant metrics from - :param error_type: Error type if an error occurred - """ - try: - # Build base attributes - always included - if operation_type == "responses": - operation_name = "responses" - elif operation_type == "conversation": - operation_name = "create_conversation" - elif operation_type == "conversation_items": - operation_name = "list_conversation_items" - else: - operation_name = operation_type - - # Extract relevant attributes from span_attributes if provided - server_address = None - server_port = None - request_model = None - - if span_attributes: - server_address = span_attributes.get(SERVER_ADDRESS) - server_port = span_attributes.get(SERVER_PORT) - request_model = span_attributes.get(GEN_AI_REQUEST_MODEL) - - # Extract response-specific attributes from result if provided - response_model = None - - if result: - response_model = getattr(result, "model", None) - # service_tier = getattr(result, "service_tier", None) # Unused - - # Use response model if available, otherwise fall back to request model - model_for_metrics = response_model or request_model - - # Record operation duration with relevant attributes - self._record_operation_duration( - duration=duration, - operation_name=operation_name, - server_address=server_address, - port=server_port, - model=model_for_metrics, - error_type=error_type, - ) - - # Record token usage metrics if result has usage information - if result and hasattr(result, "usage"): - usage = result.usage - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self._record_token_usage( - token_count=usage.prompt_tokens, - token_type="input", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self._record_token_usage( - token_count=usage.completion_tokens, - token_type="completion", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - # Handle Responses API specific token fields - if hasattr(usage, "input_tokens") and usage.input_tokens: - self._record_token_usage( - token_count=usage.input_tokens, - token_type="input", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - if hasattr(usage, "output_tokens") and usage.output_tokens: - self._record_token_usage( - token_count=usage.output_tokens, - token_type="completion", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to record metrics for %s: %s", operation_type, e) - - def instrument(self, enable_content_recording: Optional[bool] = None): - """ - Enable trace instrumentation for OpenAI Responses API. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - """ - # Check if instrumentation is enabled via environment variable - if not self._is_instrumentation_enabled(): - return # No-op if instrumentation is disabled - - if enable_content_recording is None: - enable_content_recording = self._str_to_bool( - os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "false") - ) - - # Check if binary data tracing is enabled - enable_binary_data = self._str_to_bool(os.environ.get("AZURE_TRACING_GEN_AI_INCLUDE_BINARY_DATA", "false")) - - if not self.is_instrumented(): - self._instrument_responses(enable_content_recording, enable_binary_data) - else: - self.set_enable_content_recording(enable_content_recording) - self.set_enable_binary_data(enable_binary_data) - - def uninstrument(self): - """ - Disable trace instrumentation for OpenAI Responses API. - - This method removes any active instrumentation, stopping the tracing - of OpenAI Responses API calls. - """ - if self.is_instrumented(): - self._uninstrument_responses() - - def is_instrumented(self): - """ - Check if trace instrumentation for OpenAI Responses API is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._is_instrumented() - - def set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - """This function sets the content recording value. - - :param enable_content_recording: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_recording: bool - """ - self._set_enable_content_recording(enable_content_recording=enable_content_recording) - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content tracing is enabled. - :rtype bool - """ - return self._is_content_recording_enabled() - - def set_enable_binary_data(self, enable_binary_data: bool = False) -> None: - """This function sets the binary data tracing value. - - :param enable_binary_data: Indicates whether tracing of binary data (such as images) should be enabled. - This only takes effect when content recording is also enabled. - :type enable_binary_data: bool - """ - self._set_enable_binary_data(enable_binary_data=enable_binary_data) - - def is_binary_data_enabled(self) -> bool: - """This function gets the binary data tracing value. - - :return: A bool value indicating whether binary data tracing is enabled. - :rtype: bool - """ - return self._is_binary_data_enabled() - - def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None: - for attr in attrs: - span.add_attribute(attr[0], attr[1]) - - def _set_span_attribute_safe(self, span: "AbstractSpan", key: str, value: Any) -> None: - """Safely set a span attribute only if the value is meaningful.""" - if not span or not span.span_instance.is_recording: - return - - # Only set attribute if value exists and is meaningful - if value is not None and value != "" and value != []: - span.add_attribute(key, value) - - def _parse_url(self, url): - parsed = urlparse(url) - server_address = parsed.hostname - port = parsed.port - return server_address, port - - def _create_event_attributes( - self, - conversation_id: Optional[str] = None, # pylint: disable=unused-argument - message_role: Optional[str] = None, - ) -> Dict[str, Any]: - attrs: Dict[str, Any] = {GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER} - # Removed conversation_id from event attributes as requested - it's redundant - # if conversation_id: - # attrs[GEN_AI_CONVERSATION_ID] = conversation_id - # Commented out - message_role is now included in the event content instead - # if message_role: - # attrs[GEN_AI_MESSAGE_ROLE] = message_role - return attrs - - def _append_to_message_attribute( - self, - span: "AbstractSpan", - attribute_name: str, - new_messages: List[Dict[str, Any]], - ) -> None: - """Helper to append messages to an existing attribute, combining with previous messages.""" - # Get existing attribute value - existing_value = span.span_instance.attributes.get(attribute_name) if span.span_instance.attributes else None - - if existing_value: - # Parse existing JSON array - try: - existing_messages = json.loads(existing_value) - if not isinstance(existing_messages, list): - existing_messages = [] - except (json.JSONDecodeError, TypeError): - existing_messages = [] - - # Append new messages - combined_messages = existing_messages + new_messages - else: - # No existing value, just use new messages - combined_messages = new_messages - - # Set the combined value - combined_json = json.dumps(combined_messages, ensure_ascii=False) - span.add_attribute(attribute_name, combined_json) - - def _add_message_event( - self, - span: "AbstractSpan", - role: str, - content: Optional[str] = None, - conversation_id: Optional[str] = None, - finish_reason: Optional[str] = None, - ) -> None: - """Add a message event or attribute to the span based on configuration.""" - content_array: List[Dict[str, Any]] = [] - - # Always include role and finish_reason, only include actual content if tracing is enabled - if content: - parts = [] - if _trace_responses_content: - # Include actual content when tracing is enabled - parts = [{"type": "text", "content": content}] - else: - # When content recording is off but we know there's content, include type-only structure - parts = [{"type": "text"}] - - # Create role object - role_obj: Dict[str, Any] = {"role": role} - - # Always add parts to show content structure - role_obj["parts"] = parts - - # Add finish_reason for assistant messages if available - if role == "assistant" and finish_reason: - role_obj["finish_reason"] = finish_reason - - content_array.append(role_obj) - - # Serialize the content array to JSON - json_content = json.dumps(content_array, ensure_ascii=False) - - if _get_use_message_events(): - # Original event-based implementation - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role=role, - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json_content - - # Map role to appropriate event name constant - if role == "user": - event_name = GEN_AI_USER_MESSAGE_EVENT - elif role == "assistant": - event_name = GEN_AI_ASSISTANT_MESSAGE_EVENT - else: - # Fallback for any other roles (shouldn't happen in practice) - event_name = f"gen_ai.{role}.message" - - span.span_instance.add_event(name=event_name, attributes=attributes) - else: - # New attribute-based implementation - # Append messages to the appropriate attribute (accumulating multiple messages) - if role in ("user", "tool"): - # User and tool messages go to input.messages - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - elif role == "assistant": - # Assistant messages go to output.messages - self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) - - def _add_tool_message_events( # pylint: disable=too-many-branches - self, - span: "AbstractSpan", - tool_outputs: List[Any], - conversation_id: Optional[str] = None, - ) -> None: - """Add tool message events (tool call outputs) to the span.""" - parts: List[Dict[str, Any]] = [] - - # Always iterate through tool_outputs to build type and id metadata - if tool_outputs: - for output_item in tool_outputs: - try: - tool_output: Dict[str, Any] = {} - - # Get the item type - handle both dict and object attributes - if isinstance(output_item, dict): - item_type = output_item.get("type") - else: - item_type = getattr(output_item, "type", None) - - if not item_type: - continue # Skip if no type - - # Use the API type directly - tool_output["type"] = item_type - - # Add call_id as "id" - handle both dict and object - if isinstance(output_item, dict): - call_id = output_item.get("call_id") or output_item.get("id") - else: - call_id = getattr(output_item, "call_id", None) or getattr(output_item, "id", None) - - if call_id: - tool_output["id"] = call_id - - # Add output field only if content recording is enabled - if _trace_responses_content: - # Add output field - parse JSON string if needed - if isinstance(output_item, dict): - output_value = output_item.get("output") - else: - output_value = getattr(output_item, "output", None) - - if output_value is not None: - # Try to parse JSON string into object - if isinstance(output_value, str): - try: - parsed_output = json.loads(output_value) - # For computer_call_output, strip binary data if binary tracing is disabled - if item_type == "computer_call_output" and not _trace_binary_data: - if isinstance(parsed_output, dict): - output_type = parsed_output.get("type") - # Remove image_url from computer_screenshot if binary data tracing is off - if output_type == "computer_screenshot" and "image_url" in parsed_output: - parsed_output = { - k: v for k, v in parsed_output.items() if k != "image_url" - } - tool_output["output"] = parsed_output - except (json.JSONDecodeError, TypeError): - # If parsing fails, keep as string - tool_output["output"] = output_value - else: - # For non-string output, also check for binary data in computer outputs - if item_type == "computer_call_output" and not _trace_binary_data: - if isinstance(output_value, dict): - output_type = output_value.get("type") - if output_type == "computer_screenshot" and "image_url" in output_value: - output_value = {k: v for k, v in output_value.items() if k != "image_url"} - tool_output["output"] = output_value - - # Add to parts array - # Check if simple tool format is enabled for function_call_output types - if _get_use_simple_tool_format() and item_type == "function_call_output": - # Build simplified OTEL-compliant format: - # {"type": "tool_call_response", "id": "...", "result": "..."} - simple_response: Dict[str, Any] = {"type": "tool_call_response"} - if "id" in tool_output: - simple_response["id"] = tool_output["id"] - if _trace_responses_content and "output" in tool_output: - simple_response["result"] = tool_output["output"] - parts.append(simple_response) - else: - # Original nested format (type "tool_call_output" wraps the tool output) - # Always include type and id, even when content recording is disabled - parts.append({"type": "tool_call_output", "content": tool_output}) - except Exception: # pylint: disable=broad-exception-caught - # Skip items that can't be processed - logger.debug( - "Failed to process tool output item: %s", - output_item, - exc_info=True, - ) - continue - - # Always include parts array with type and id, even when content recording is disabled - content_array = [{"role": "tool", "parts": parts}] if parts else [] - - if _get_use_message_events(): - # Event-based mode: add events - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="tool", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Use "tool" for the event name: gen_ai.tool.message - span.span_instance.add_event(name=GEN_AI_TOOL_MESSAGE_EVENT, attributes=attributes) - else: - # Attribute-based mode: append to input messages (tool outputs are inputs to the model) - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - - def _add_mcp_response_events( - self, - span: "AbstractSpan", - mcp_responses: List[Any], - conversation_id: Optional[str] = None, - ) -> None: - """Add MCP response events (user-provided responses like approval) to the span.""" - parts: List[Dict[str, Any]] = [] - - # Always iterate through mcp_responses to build metadata - if mcp_responses: - for response_item in mcp_responses: - try: - mcp_response: Dict[str, Any] = {} - - # Get the item type - handle both dict and object attributes - if isinstance(response_item, dict): - item_type = response_item.get("type") - else: - item_type = getattr(response_item, "type", None) - - if not item_type: - continue # Skip if no type - - # Use the full MCP type (e.g., "mcp_approval_response") - mcp_response["type"] = item_type - - # Add id/approval_request_id - handle both dict and object - if isinstance(response_item, dict): - response_id = response_item.get("id") or response_item.get("approval_request_id") - else: - response_id = getattr(response_item, "id", None) or getattr( - response_item, "approval_request_id", None - ) - - if response_id: - mcp_response["id"] = response_id - - # Add additional fields only if content recording is enabled - if _trace_responses_content: - # Add approval-specific fields - if isinstance(response_item, dict): - for field in ["approve", "approval_request_id", "status"]: - if field in response_item and response_item[field] is not None: - mcp_response[field] = response_item[field] - else: - for field in ["approve", "approval_request_id", "status"]: - if hasattr(response_item, field): - value = getattr(response_item, field) - if value is not None: - mcp_response[field] = value - - # Add to parts array (type "mcp" wraps the MCP response) - # Always include type and id, even when content recording is disabled - parts.append({"type": "mcp", "content": mcp_response}) - except Exception: # pylint: disable=broad-exception-caught - # Skip items that can't be processed - logger.debug( - "Failed to process MCP response item: %s", - response_item, - exc_info=True, - ) - continue - - # Always include parts array with type and id, even when content recording is disabled - content_array = [{"role": "user", "parts": parts}] if parts else [] - - if _get_use_message_events(): - # Event-based mode: add events - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="user", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Use user message event name since MCP responses are user inputs - span.span_instance.add_event(name=GEN_AI_USER_MESSAGE_EVENT, attributes=attributes) - else: - # Attribute-based mode: append to input messages (MCP responses are user inputs) - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - - def _add_workflow_action_events( - self, - span: "AbstractSpan", - response: Any, - conversation_id: Optional[str] = None, - ) -> None: - """Add workflow action events to the span for workflow agents.""" - if not span or not span.span_instance.is_recording: - return - - # Check if response has output items - if not hasattr(response, "output") or not response.output: - return - - # Iterate through output items looking for workflow_action types - for output_item in response.output: - item_type = getattr(output_item, "type", None) - - if item_type == "workflow_action": - # Extract workflow action attributes - action_id = getattr(output_item, "action_id", None) - status = getattr(output_item, "status", None) - previous_action_id = getattr(output_item, "previous_action_id", None) - workflow_action_id = getattr(output_item, "id", None) - - # Create event attributes - event_attributes = { - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - } - - # Build workflow action details object - workflow_details: Dict[str, Any] = {} - - if _trace_responses_content: - # Include action details in content using optimized format - if status: - workflow_details["status"] = status - if action_id: - workflow_details["action_id"] = action_id - if previous_action_id: - workflow_details["previous_action_id"] = previous_action_id - else: - # When content recording is off, only include status - if status: - workflow_details["status"] = status - - # Use consistent format with role and parts wrapper - content_array = [ - { - "role": "workflow", - "parts": [{"type": "workflow_action", "content": workflow_details}], - } - ] - - # Store as JSON array directly without outer wrapper - event_attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Add the workflow action event - span.span_instance.add_event(name=GEN_AI_WORKFLOW_ACTION_EVENT, attributes=event_attributes) - - # pylint: disable=too-many-branches - def _add_structured_input_events( - self, - span: "AbstractSpan", - input_list: List[Any], - conversation_id: Optional[str] = None, - ) -> None: - """ - Add message events for structured input (list format). - This handles cases like messages with images, multi-part content, etc. - """ - for input_item in input_list: - try: - # Extract role - handle both dict and object - if isinstance(input_item, dict): - role = input_item.get("role", "user") - content = input_item.get("content") - else: - role = getattr(input_item, "role", "user") - content = getattr(input_item, "content", None) - - if not content: - continue - - # Build parts array with content parts - parts: List[Dict[str, Any]] = [] - has_content = False - - # Content can be a list of content items - if isinstance(content, list): - for content_item in content: - content_type = None - has_content = True - - # Handle dict format - if isinstance(content_item, dict): - content_type = content_item.get("type") - if content_type in ("input_text", "text"): - if _trace_responses_content: - text = content_item.get("text") - if text: - parts.append({"type": "text", "content": text}) - else: - parts.append({"type": "text"}) - elif content_type == "input_image": - image_part: Dict[str, Any] = {"type": "image"} - # Include image data if binary data tracing is enabled - if _trace_responses_content and _trace_binary_data: - image_url = content_item.get("image_url") - if image_url: - image_part["content"] = image_url - parts.append(image_part) - elif content_type == "input_file": - file_part: Dict[str, Any] = {"type": "file"} - if _trace_responses_content: - file_content: Dict[str, Any] = {} - # Only include filename and file_id if content recording is enabled - filename = content_item.get("filename") - if filename: - file_content["filename"] = filename - file_id = content_item.get("file_id") - if file_id: - file_content["file_id"] = file_id - # Only include file_data if binary data tracing is enabled - if _trace_binary_data: - file_data = content_item.get("file_data") - if file_data: - file_content["file_data"] = file_data - if file_content: - file_part["content"] = file_content - parts.append(file_part) - elif content_type: - # Other content types (audio, video, etc.) - parts.append({"type": content_type}) - - # Handle object format - elif hasattr(content_item, "type"): - content_type = getattr(content_item, "type", None) - if content_type in ("input_text", "text"): - if _trace_responses_content: - text = getattr(content_item, "text", None) - if text: - parts.append({"type": "text", "content": text}) - else: - parts.append({"type": "text"}) - elif content_type == "input_image": - image_part = {"type": "image"} - # Include image data if binary data tracing is enabled - if _trace_responses_content and _trace_binary_data: - image_url = getattr(content_item, "image_url", None) - if image_url: - image_part["content"] = image_url - parts.append(image_part) - elif content_type == "input_file": - file_part = {"type": "file"} - if _trace_responses_content: - file_content = {} - # Only include filename and file_id if content recording is enabled - filename = getattr(content_item, "filename", None) - if filename: - file_content["filename"] = filename - file_id = getattr(content_item, "file_id", None) - if file_id: - file_content["file_id"] = file_id - # Only include file_data if binary data tracing is enabled - if _trace_binary_data: - file_data = getattr(content_item, "file_data", None) - if file_data: - file_content["file_data"] = file_data - if file_content: - file_part["content"] = file_content - parts.append(file_part) - elif content_type: - # Other content types - parts.append({"type": content_type}) - - # Always create role object and include parts if we detected content - role_obj: Dict[str, Any] = {"role": role} - if parts: - role_obj["parts"] = parts - content_array = [role_obj] - - if _get_use_message_events(): - # Event-based mode - # Create event attributes - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role=role, - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Map role to appropriate event name constant - if role == "user": - event_name = GEN_AI_USER_MESSAGE_EVENT - elif role == "assistant": - event_name = GEN_AI_ASSISTANT_MESSAGE_EVENT - else: - # Fallback for any other roles (shouldn't happen in practice) - event_name = f"gen_ai.{role}.message" - - # Add the event - span.span_instance.add_event(name=event_name, attributes=attributes) - else: - # Attribute-based mode - # Append messages to the appropriate attribute - if role in ("user", "tool"): - # User and tool messages go to input.messages - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - elif role == "assistant": - # Assistant messages go to output.messages - self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) - - except Exception: # pylint: disable=broad-exception-caught - # Skip items that can't be processed - logger.debug( - "Failed to process structured input item: %s", - input_item, - exc_info=True, - ) - continue - - def _emit_tool_call_event( - self, - span: "AbstractSpan", - tool_call: Dict[str, Any], - conversation_id: Optional[str] = None, - ) -> None: - """Helper to emit a single tool call event or attribute.""" - # Check if simple tool format is enabled for function_call types - if _get_use_simple_tool_format() and tool_call.get("type") == "function_call": - # Build simplified OTEL-compliant format: - # {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} - simple_tool_call: Dict[str, Any] = {"type": "tool_call"} - if "id" in tool_call: - simple_tool_call["id"] = tool_call["id"] - # Extract name and arguments from nested function object if content recording enabled - if _trace_responses_content and "function" in tool_call: - func = tool_call["function"] - if "name" in func: - simple_tool_call["name"] = func["name"] - if "arguments" in func: - simple_tool_call["arguments"] = func["arguments"] - parts = [simple_tool_call] - else: - # Original nested format - parts = [{"type": "tool_call", "content": tool_call}] - - content_array = [{"role": "assistant", "parts": parts}] - - if _get_use_message_events(): - # Original event-based implementation - json_content = json.dumps(content_array, ensure_ascii=False) - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="assistant", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json_content - span.span_instance.add_event(name=GEN_AI_ASSISTANT_MESSAGE_EVENT, attributes=attributes) - else: - # New attribute-based implementation - tool calls are output messages - self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) - - def _emit_tool_output_event( - self, - span: "AbstractSpan", - tool_output: Dict[str, Any], - conversation_id: Optional[str] = None, - ) -> None: - """Helper to emit a single tool output event or attribute.""" - # Check if simple tool format is enabled for function_call_output types - if _get_use_simple_tool_format() and tool_output.get("type") == "function_call_output": - # Build simplified OTEL-compliant format: - # {"type": "tool_call_response", "id": "...", "result": "..."} - simple_tool_response: Dict[str, Any] = {"type": "tool_call_response"} - if "id" in tool_output: - simple_tool_response["id"] = tool_output["id"] - # Add result only if content recording is enabled - if _trace_responses_content and "output" in tool_output: - simple_tool_response["result"] = tool_output["output"] - parts = [simple_tool_response] - else: - # Original nested format - parts = [{"type": "tool_call_output", "content": tool_output}] - - content_array = [{"role": "tool", "parts": parts}] - - if _get_use_message_events(): - # Original event-based implementation - json_content = json.dumps(content_array, ensure_ascii=False) - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="tool", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json_content - # Tool outputs are inputs to the model, so use input.messages event - span.span_instance.add_event(name=GEN_AI_USER_MESSAGE_EVENT, attributes=attributes) - else: - # New attribute-based implementation - tool outputs are input messages - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - - def _add_tool_call_events( # pylint: disable=too-many-branches - self, - span: "AbstractSpan", - response: Any, - conversation_id: Optional[str] = None, - ) -> None: - """Add tool call events to the span from response output.""" - if not span or not span.span_instance.is_recording: - return - - # Extract function calls and tool calls from response output - output = getattr(response, "output", None) - if not output: - return - - # Process output items for tool call events - for output_item in output: - try: - item_type = getattr(output_item, "type", None) - # Process item based on type - if not item_type: - continue - - tool_call: Dict[str, Any] # Declare once for all branches - - # Handle function_call type - if item_type == "function_call": - tool_call = { - "type": item_type, - } - - # Always include id (needed to correlate with function output) - if hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include function name and arguments if content recording is enabled - if _trace_responses_content: - function_details: Dict[str, Any] = {} - if hasattr(output_item, "name"): - function_details["name"] = output_item.name - if hasattr(output_item, "arguments"): - function_details["arguments"] = output_item.arguments - if function_details: - tool_call["function"] = function_details - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle file_search_call type - elif item_type == "file_search_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include search details if content recording is enabled - if _trace_responses_content: - # queries and results are directly on the item - if hasattr(output_item, "queries") and output_item.queries: - tool_call["queries"] = output_item.queries - if hasattr(output_item, "results") and output_item.results: - tool_call["results"] = [] - for result in output_item.results: - result_data = { - "file_id": getattr(result, "file_id", None), - "filename": getattr(result, "filename", None), - "score": getattr(result, "score", None), - } - tool_call["results"].append(result_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle code_interpreter_call type - elif item_type == "code_interpreter_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include code interpreter details if content recording is enabled - if _trace_responses_content: - # code and outputs are directly on the item - if hasattr(output_item, "code") and output_item.code: - tool_call["code"] = output_item.code - if hasattr(output_item, "outputs") and output_item.outputs: - tool_call["outputs"] = [] - for output in output_item.outputs: - # Outputs can be logs or images - output_data = { - "type": getattr(output, "type", None), - } - if hasattr(output, "logs"): - output_data["logs"] = output.logs - elif hasattr(output, "image"): - output_data["image"] = {"file_id": getattr(output.image, "file_id", None)} - tool_call["outputs"].append(output_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle web_search_call type - elif item_type == "web_search_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include search action if content recording is enabled - if _trace_responses_content: - # action is directly on the item - if hasattr(output_item, "action") and output_item.action: - # WebSearchAction has type and type-specific fields - tool_call["action"] = { - "type": getattr(output_item.action, "type", None), - } - # Try to capture action-specific fields - if hasattr(output_item.action, "query"): - tool_call["action"]["query"] = output_item.action.query - if hasattr(output_item.action, "results"): - tool_call["action"]["results"] = [] - for result in output_item.action.results: - result_data = { - "title": getattr(result, "title", None), - "url": getattr(result, "url", None), - } - tool_call["action"]["results"].append(result_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle azure_ai_search_call type - elif item_type == "azure_ai_search_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include search details if content recording is enabled - if _trace_responses_content: - # Add Azure AI Search specific fields - if hasattr(output_item, "input") and output_item.input: - tool_call["input"] = output_item.input - - if hasattr(output_item, "results") and output_item.results: - tool_call["results"] = [] - for result in output_item.results: - result_data = {} - if hasattr(result, "title"): - result_data["title"] = result.title - if hasattr(result, "url"): - result_data["url"] = result.url - if hasattr(result, "content"): - result_data["content"] = result.content - if result_data: - tool_call["results"].append(result_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle image_generation_call type - elif item_type == "image_generation_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include image generation details if content recording is enabled - if _trace_responses_content: - # Include metadata fields - if hasattr(output_item, "prompt"): - tool_call["prompt"] = output_item.prompt - if hasattr(output_item, "quality"): - tool_call["quality"] = output_item.quality - if hasattr(output_item, "size"): - tool_call["size"] = output_item.size - if hasattr(output_item, "style"): - tool_call["style"] = output_item.style - - # Include the result (image data) only if binary data tracing is enabled - if _trace_binary_data and hasattr(output_item, "result") and output_item.result: - tool_call["result"] = output_item.result - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle mcp_call type (Model Context Protocol) - elif item_type == "mcp_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include MCP details if content recording is enabled - if _trace_responses_content: - if hasattr(output_item, "name"): - tool_call["name"] = output_item.name - if hasattr(output_item, "arguments"): - tool_call["arguments"] = output_item.arguments - if hasattr(output_item, "server_label"): - tool_call["server_label"] = output_item.server_label - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle other MCP types (mcp_list_tools, mcp_approval_request, etc.) - elif item_type and item_type.startswith("mcp_"): - tool_call = { - "type": item_type, # Preserve the specific MCP type - } - - # Always include ID if available - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include additional details if content recording is enabled - if _trace_responses_content: - # Try to capture common MCP fields - for field in [ - "name", - "server_label", - "arguments", - "approval_request_id", - "approve", - "status", - ]: - if hasattr(output_item, field): - value = getattr(output_item, field) - if value is not None: - tool_call[field] = value - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle computer_call type (for computer use) - elif item_type == "computer_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "call_id"): - tool_call["call_id"] = output_item.call_id - - # Only include computer action details if content recording is enabled - if _trace_responses_content: - # action is directly on the item - if hasattr(output_item, "action") and output_item.action: - # ComputerAction has type and type-specific fields - tool_call["action"] = { - "type": getattr(output_item.action, "type", None), - } - # Try to capture common action fields - for attr in ["x", "y", "text", "key", "command", "scroll"]: - if hasattr(output_item.action, attr): - tool_call["action"][attr] = getattr(output_item.action, attr) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle remote_function_call_output type (remote tool calls like Azure AI Search) - elif item_type == "remote_function_call_output": - # Extract the tool name from the output item - tool_name = getattr(output_item, "name", None) if hasattr(output_item, "name") else None - - tool_call = { - "type": tool_name if tool_name else "remote_function", - } - - # Always include ID (needed for correlation) - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - # Check model_extra for call_id - elif hasattr(output_item, "model_extra") and isinstance(output_item.model_extra, dict): - if "call_id" in output_item.model_extra: - tool_call["id"] = output_item.model_extra["call_id"] - - # Only include tool details if content recording is enabled - if _trace_responses_content: - # Extract data from model_extra if available (Pydantic v2 style) - if hasattr(output_item, "model_extra") and isinstance(output_item.model_extra, dict): - for key, value in output_item.model_extra.items(): - # Skip already captured fields, redundant fields (name, label), and empty/None values - if ( - key not in ["type", "id", "call_id", "name", "label"] - and value is not None - and value != "" - ): - tool_call[key] = value - - # Also try as_dict if available - if hasattr(output_item, "as_dict"): - try: - tool_dict = output_item.as_dict() - # Extract relevant fields (exclude already captured ones and empty/None values) - for key, value in tool_dict.items(): - if key not in [ - "type", - "id", - "call_id", - "name", - "label", - "role", - "content", - ]: - # Skip empty strings and None values - if value is not None and value != "": - # Don't overwrite if already exists - if key not in tool_call: - tool_call[key] = value - except Exception as e: - logger.debug(f"Failed to extract data from as_dict: {e}") - - # Fallback: try common fields directly (skip if empty and skip redundant name/label) - for field in [ - "input", - "output", - "results", - "status", - "error", - "search_query", - "query", - ]: - if hasattr(output_item, field): - try: - value = getattr(output_item, field) - if value is not None and value != "": - # If not already in tool_call, add it - if field not in tool_call: - tool_call[field] = value - except Exception: - pass - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle unknown/future tool call types with best effort - # Exclude _output types - those are handled separately as tool outputs, not tool calls - elif item_type and "_call" in item_type and not item_type.endswith("_output"): - # Generic handler for tool calls - try: - tool_call = { - "type": item_type, - } - - # Always try to include common ID fields (safe, needed for correlation) - for id_field in ["id", "call_id"]: - if hasattr(output_item, id_field): - tool_call["id" if id_field == "id" else "id"] = getattr(output_item, id_field) - break # Use first available ID field - - # Only include detailed fields if content recording is enabled - if _trace_responses_content: - # Try to get the full tool details using model_dump() for Pydantic models - if hasattr(output_item, "model_dump"): - tool_dict = output_item.model_dump() - # Extract the tool-specific details (exclude common fields already captured) - for key, value in tool_dict.items(): - if ( - key - not in ["type", "id", "call_id", "role", "content", "status", "partition_key"] - and value is not None - ): - tool_call[key] = value - elif hasattr(output_item, "as_dict"): - tool_dict = output_item.as_dict() - # Extract the tool-specific details (exclude common fields already captured) - for key, value in tool_dict.items(): - if key not in ["type", "id", "call_id"] and value is not None: - tool_call[key] = value - else: - # Fallback: try to capture common fields manually - for field in [ - "name", - "arguments", - "input", - "query", - "search_query", - "server_label", - ]: - if hasattr(output_item, field): - value = getattr(output_item, field) - if value is not None: - tool_call[field] = value - - self._emit_tool_call_event(span, tool_call, conversation_id) - - except Exception as e: - # Log but don't crash if we can't handle an unknown tool type - logger.debug(f"Failed to process unknown tool call type '{item_type}': {e}") - - # Handle unknown/future tool output types with best effort - # These are the _output types that correspond to the tool calls above - elif item_type and item_type.endswith("_output"): - # Generic handler for tool outputs - try: - tool_output = { - "type": item_type, - } - - # Always try to include common ID fields (safe, needed for correlation) - for id_field in ["id", "call_id"]: - if hasattr(output_item, id_field): - tool_output["id"] = getattr(output_item, id_field) - break # Use first available ID field - - # Only include detailed fields if content recording is enabled - if _trace_responses_content: - # Try to get the full tool output using model_dump() for Pydantic models - if hasattr(output_item, "model_dump"): - output_dict = output_item.model_dump() - # Extract the tool-specific output (exclude common fields already captured) - # Include fields even if empty string (but not None) for API consistency - for key, value in output_dict.items(): - if ( - key - not in ["type", "id", "call_id", "role", "content", "status", "partition_key"] - and value is not None - ): - tool_output[key] = value - elif hasattr(output_item, "as_dict"): - output_dict = output_item.as_dict() - # Extract the tool-specific output (exclude common fields already captured) - for key, value in output_dict.items(): - if ( - key not in ["type", "id", "call_id", "role", "content", "status"] - and value is not None - ): - tool_output[key] = value - else: - # Fallback: try to capture common output fields manually - for field in [ - "output", - "result", - "results", - "data", - "response", - ]: - if hasattr(output_item, field): - value = getattr(output_item, field) - if value is not None: - tool_output[field] = value - - self._emit_tool_output_event(span, tool_output, conversation_id) - - except Exception as e: - # Log but don't crash if we can't handle an unknown tool output type - logger.debug(f"Failed to process unknown tool output type '{item_type}': {e}") - - except Exception as e: - # Catch-all to prevent any tool call processing errors from breaking instrumentation - logger.debug(f"Error processing tool call events: {e}") - - def start_responses_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - model: Optional[str] = None, - assistant_name: Optional[str] = None, - agent_id: Optional[str] = None, - conversation_id: Optional[str] = None, - input_text: Optional[str] = None, - input_raw: Optional[Any] = None, - stream: bool = False, # pylint: disable=unused-argument - tools: Optional[List[Dict[str, Any]]] = None, - ) -> "Optional[AbstractSpan]": - """Start a span for responses API call.""" - # Build span name: agent case uses "invoke_agent", non-agent case uses "chat" - if assistant_name: - span_name = f"{SPAN_NAME_INVOKE_AGENT} {assistant_name}" - operation_name_value = OPERATION_NAME_INVOKE_AGENT - elif model: - span_name = f"{SPAN_NAME_CHAT} {model}" - operation_name_value = OPERATION_NAME_CHAT - else: - span_name = OperationName.RESPONSES.value - operation_name_value = OperationName.RESPONSES.value - - span = start_span( - operation_name=OperationName.RESPONSES, - server_address=server_address, - port=port, - span_name=span_name, - model=model, - gen_ai_provider=RESPONSES_PROVIDER, - ) - - if span and span.span_instance.is_recording: - # Set operation name attribute (start_span doesn't set this automatically) - self._set_attributes( - span, - (GEN_AI_OPERATION_NAME, operation_name_value), - ) - - # Set response-specific attributes that start_span doesn't handle - # Note: model and server_address are already set by start_span, so we don't need to set them again - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - self._set_span_attribute_safe(span, GEN_AI_AGENT_NAME, assistant_name) - self._set_span_attribute_safe(span, GEN_AI_AGENT_ID, agent_id) - - # Set tools attribute if tools are provided - if tools: - # Convert tools list to JSON string for the attribute - tools_json = json.dumps(tools, ensure_ascii=False) - self._set_span_attribute_safe(span, GEN_AI_REQUEST_TOOLS, tools_json) - - # Process input - check if it contains tool outputs or MCP responses - tool_outputs = [] - mcp_responses = [] - has_tool_outputs = False - has_mcp_responses = False - - # Use input_raw (or input_text if it's a list) to check for tool outputs - input_to_check = input_raw if input_raw is not None else input_text - - # Check if input is a list (structured input with potential tool outputs) - if isinstance(input_to_check, list): - for item in input_to_check: - # Check if this item has type "function_call_output" or similar - item_type = None - if hasattr(item, "type"): - item_type = getattr(item, "type", None) - elif isinstance(item, dict): - item_type = item.get("type") - - if item_type and ("output" in item_type or item_type == "function_call_output"): - has_tool_outputs = True - tool_outputs.append(item) - elif item_type and item_type.startswith("mcp_") and "response" in item_type: - # MCP responses (mcp_approval_response, etc.) are user inputs - has_mcp_responses = True - mcp_responses.append(item) - - # Add appropriate message events based on input type - if has_tool_outputs: - # Add tool message event for tool outputs - self._add_tool_message_events( - span, - tool_outputs=tool_outputs, - conversation_id=conversation_id, - ) - elif has_mcp_responses: - # Add MCP response events (user providing approval/response) - self._add_mcp_response_events( - span, - mcp_responses=mcp_responses, - conversation_id=conversation_id, - ) - elif input_text and not isinstance(input_text, list): - # Add regular user message event (only if input_text is a string, not a list) - self._add_message_event( - span, - role="user", - content=input_text, - conversation_id=conversation_id, - ) - elif isinstance(input_to_check, list) and not has_tool_outputs: - # Handle structured input (list format) - extract text content from user messages - # This handles cases like image inputs with text prompts - self._add_structured_input_events( - span, - input_list=input_to_check, - conversation_id=conversation_id, - ) - - return span - - def _extract_server_info_from_client( - self, client: Any - ) -> Tuple[Optional[str], Optional[int]]: # pylint: disable=docstring-missing-return,docstring-missing-rtype - """Extract server address and port from OpenAI client.""" - try: - # First try direct access to base_url - if hasattr(client, "base_url") and client.base_url: - return self._parse_url(str(client.base_url)) - if hasattr(client, "_base_url") and client._base_url: # pylint: disable=protected-access - return self._parse_url(str(client._base_url)) - - # Try the nested client structure as suggested - base_client = getattr(client, "_client", None) - if base_client: - base_url = getattr(base_client, "base_url", None) - if base_url: - return self._parse_url(str(base_url)) - except Exception: # pylint: disable=broad-exception-caught - pass - return None, None - - def _extract_conversation_id(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract conversation ID from kwargs.""" - return kwargs.get("conversation") or kwargs.get("conversation_id") - - def _extract_model(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract model from kwargs.""" - return kwargs.get("model") - - def _extract_assistant_name(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract assistant/agent name from kwargs.""" - extra_body = kwargs.get("extra_body") - if extra_body and isinstance(extra_body, dict): - agent_info = extra_body.get("agent_reference") - if agent_info and isinstance(agent_info, dict): - return agent_info.get("name") - return None - - def _extract_agent_id(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract agent ID from kwargs.""" - extra_body = kwargs.get("extra_body") - if extra_body and isinstance(extra_body, dict): - agent_info = extra_body.get("agent_reference") - if agent_info and isinstance(agent_info, dict): - return agent_info.get("id") - return None - - def _extract_input_text(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract input text from kwargs.""" - return kwargs.get("input") - - def _extract_finish_reason(self, response: Any) -> Optional[str]: - """Extract finish reason from response output.""" - if hasattr(response, "output") and response.output: - try: - # Check if output is a list (typical case) - if isinstance(response.output, list) and len(response.output) > 0: - output_item = response.output[0] # Get first output item - - # Try finish_reason field - if hasattr(output_item, "finish_reason") and output_item.finish_reason: - return output_item.finish_reason - - # Try finish_details.type (Azure AI Agents structure) - if hasattr(output_item, "finish_details") and output_item.finish_details: - if hasattr(output_item.finish_details, "type"): - return output_item.finish_details.type - except (AttributeError, TypeError, IndexError): - pass - - # Fallback: check response.status directly - if hasattr(response, "status"): - return response.status - - return None - - def _extract_output_text(self, response: Any) -> Optional[str]: - """Extract output text from response.""" - if hasattr(response, "output") and response.output: - # Handle simple string output (for tests/simple cases) - if isinstance(response.output, str): - return response.output - - # Handle complex output structure (list of response messages) - output_texts = [] - try: - for output_item in response.output: - if hasattr(output_item, "content") and output_item.content: - # content is typically a list of content blocks - for content_block in output_item.content: - if hasattr(content_block, "text"): - output_texts.append(content_block.text) - elif hasattr(content_block, "output_text") and hasattr(content_block.output_text, "text"): - # Handle ResponseOutputText structure - output_texts.append(content_block.output_text.text) - elif isinstance(content_block, str): - output_texts.append(content_block) - elif isinstance(output_item, str): - # Handle simple string items - output_texts.append(output_item) - - if output_texts: - return " ".join(output_texts) - except (AttributeError, TypeError): - # Fallback: convert to string but log for debugging - logger.debug( - "Failed to extract structured output text, falling back to string conversion: %s", - response.output, - ) - return str(response.output) - return None - - def _extract_responses_api_attributes(self, span: "AbstractSpan", response: Any) -> None: - """Extract and set attributes for Responses API responses.""" - try: - # Extract and set response model - model = getattr(response, "model", None) - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_MODEL, model) - - # Extract and set response ID - response_id = getattr(response, "id", None) - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_ID, response_id) - - # Extract and set system fingerprint if available - system_fingerprint = getattr(response, "system_fingerprint", None) - self._set_span_attribute_safe(span, GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT, system_fingerprint) - - # Extract and set usage information (Responses API may use input_tokens/output_tokens) - usage = getattr(response, "usage", None) - if usage: - # Try input_tokens first, then prompt_tokens for compatibility - input_tokens = getattr(usage, "input_tokens", None) or getattr(usage, "prompt_tokens", None) - # Try output_tokens first, then completion_tokens for compatibility - output_tokens = getattr(usage, "output_tokens", None) or getattr(usage, "completion_tokens", None) - # total_tokens = getattr(usage, "total_tokens", None) # Unused - - self._set_span_attribute_safe(span, GEN_AI_USAGE_INPUT_TOKENS, input_tokens) - self._set_span_attribute_safe(span, GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens) - # self._set_span_attribute_safe(span, GEN_AI_USAGE_TOTAL_TOKENS, total_tokens) # Commented out as redundant - - # Extract finish reasons from output items (Responses API structure) - output = getattr(response, "output", None) - if output: - finish_reasons = [] - for item in output: - if hasattr(item, "finish_reason") and item.finish_reason: - finish_reasons.append(item.finish_reason) - - if finish_reasons: - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_FINISH_REASONS, finish_reasons) - else: - # Handle single finish reason (not in output array) - finish_reason = getattr(response, "finish_reason", None) - if finish_reason: - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_FINISH_REASONS, [finish_reason]) - - except Exception as e: - logger.debug(f"Error extracting responses API attributes: {e}") - - def _extract_conversation_attributes(self, span: "AbstractSpan", response: Any) -> None: - """Extract and set attributes for conversation creation responses.""" - try: - # Extract and set conversation ID - conversation_id = getattr(response, "id", None) - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - - # Set response object type - # self._set_span_attribute_safe(span, GEN_AI_RESPONSE_OBJECT, "conversation") - - except Exception as e: - logger.debug(f"Error extracting conversation attributes: {e}") - - def _extract_conversation_items_attributes( - self, span: "AbstractSpan", response: Any, args: Tuple, kwargs: Dict[str, Any] - ) -> None: - """Extract and set attributes for conversation items list responses.""" - try: - # Set response object type for list operations - # self._set_span_attribute_safe(span, GEN_AI_RESPONSE_OBJECT, "list") - - # Extract conversation_id from request parameters - conversation_id = None - if args and len(args) > 1: - # Second argument might be conversation_id - conversation_id = args[1] - elif "conversation_id" in kwargs: - conversation_id = kwargs["conversation_id"] - - if conversation_id: - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - - # Note: Removed gen_ai.response.has_more attribute as requested - - except Exception as e: - logger.debug(f"Error extracting conversation items attributes: {e}") - - def _extract_response_attributes(self, response: Any) -> Dict[str, Any]: - """Extract response attributes from response object (legacy method for backward compatibility).""" - attributes = {} - - try: - # Extract response model - model = getattr(response, "model", None) - if model: - attributes[GEN_AI_RESPONSE_MODEL] = model - - # Extract response ID - response_id = getattr(response, "id", None) - if response_id: - attributes[GEN_AI_RESPONSE_ID] = response_id - - # Extract usage information - usage = getattr(response, "usage", None) - if usage: - prompt_tokens = getattr(usage, "prompt_tokens", None) - completion_tokens = getattr(usage, "completion_tokens", None) - # total_tokens = getattr(usage, "total_tokens", None) # Unused - - if prompt_tokens: - attributes[GEN_AI_USAGE_INPUT_TOKENS] = prompt_tokens - if completion_tokens: - attributes[GEN_AI_USAGE_OUTPUT_TOKENS] = completion_tokens - # if total_tokens: - # attributes[GEN_AI_USAGE_TOTAL_TOKENS] = total_tokens # Commented out as redundant - - # Extract finish reasons from output items (Responses API structure) - output = getattr(response, "output", None) - if output: - finish_reasons = [] - for item in output: - if hasattr(item, "finish_reason") and item.finish_reason: - finish_reasons.append(item.finish_reason) - - if finish_reasons: - attributes[GEN_AI_RESPONSE_FINISH_REASONS] = finish_reasons - else: - finish_reason = getattr(response, "finish_reason", None) - if finish_reason: - attributes[GEN_AI_RESPONSE_FINISH_REASONS] = [finish_reason] - - except Exception as e: - logger.debug(f"Error extracting response attributes: {e}") - - return attributes - - def _create_responses_span_from_parameters(self, *args, **kwargs): - """Extract parameters and create span for responses API tracing.""" - # Extract client from args (first argument) - client = args[0] if args else None - server_address, port = self._extract_server_info_from_client(client) - - # Extract parameters from kwargs - conversation_id = self._extract_conversation_id(kwargs) - model = self._extract_model(kwargs) - assistant_name = self._extract_assistant_name(kwargs) - agent_id = self._extract_agent_id(kwargs) - input_text = self._extract_input_text(kwargs) - input_raw = kwargs.get("input") # Get the raw input (could be string or list) - stream = kwargs.get("stream", False) - - # Create and return the span - return self.start_responses_span( - server_address=server_address, - port=port, - model=model, - assistant_name=assistant_name, - agent_id=agent_id, - conversation_id=conversation_id, - input_text=input_text, - input_raw=input_raw, - stream=stream, - ) - - def trace_responses_create(self, function, *args, **kwargs): - """Trace synchronous responses.create calls.""" - # If stream=True and we're being called from responses.stream(), skip tracing - # The responses.stream() method internally calls create(stream=True), and - # trace_responses_stream() will handle the tracing for that case. - # We only trace direct calls to create(stream=True) from user code. - if kwargs.get("stream", False): - # Check if we're already in a stream tracing context - # by looking at the call stack - import inspect - - frame = inspect.currentframe() - if frame and frame.f_back and frame.f_back.f_back: - # Check if the caller is trace_responses_stream - caller_name = frame.f_back.f_back.f_code.co_name - if caller_name in ( - "trace_responses_stream", - "trace_responses_stream_async", - "__enter__", - "__aenter__", - ): - # We're being called from responses.stream(), don't create a new span - return function(*args, **kwargs) - - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Handle streaming vs non-streaming responses differently - stream = kwargs.get("stream", False) - if stream: - # For streaming, don't use context manager - let wrapper handle span lifecycle - try: - result = function(*args, **kwargs) - result = self._wrap_streaming_response( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - else: - # For non-streaming, use context manager as before - with span: - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set response attributes - self._extract_responses_api_attributes(span, result) - - # Add tool call events (if any) - conversation_id = self._extract_conversation_id(kwargs) - self._add_tool_call_events(span, result, conversation_id) - - # Add workflow action events (if any) - self._add_workflow_action_events(span, result, conversation_id) - - # Add assistant message event - output_text = self._extract_output_text(result) - if output_text: - finish_reason = self._extract_finish_reason(result) - self._add_message_event( - span, - role="assistant", - content=output_text, - conversation_id=conversation_id, - finish_reason=finish_reason, - ) - - # Record metrics using new dedicated method - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.OK) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - return result - - async def trace_responses_create_async(self, function, *args, **kwargs): - """Trace asynchronous responses.create calls.""" - # If stream=True and we're being called from responses.stream(), skip tracing - # The responses.stream() method internally calls create(stream=True), and - # trace_responses_stream() will handle the tracing for that case. - # We only trace direct calls to create(stream=True) from user code. - if kwargs.get("stream", False): - # Check if we're already in a stream tracing context - # by looking at the call stack - import inspect - - frame = inspect.currentframe() - if frame and frame.f_back and frame.f_back.f_back: - # Check if the caller is trace_responses_stream - caller_name = frame.f_back.f_back.f_code.co_name - if caller_name in ( - "trace_responses_stream", - "trace_responses_stream_async", - "__enter__", - "__aenter__", - ): - # We're being called from responses.stream(), don't create a new span - return await function(*args, **kwargs) - - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Handle streaming vs non-streaming responses differently - stream = kwargs.get("stream", False) - if stream: - # For streaming, don't use context manager - let wrapper handle span lifecycle - try: - result = await function(*args, **kwargs) - result = self._wrap_async_streaming_response( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - else: - # For non-streaming, use context manager as before - with span: - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set response attributes - self._extract_responses_api_attributes(span, result) - - # Add tool call events (if any) - conversation_id = self._extract_conversation_id(kwargs) - self._add_tool_call_events(span, result, conversation_id) - - # Add workflow action events (if any) - self._add_workflow_action_events(span, result, conversation_id) - - # Add assistant message event - output_text = self._extract_output_text(result) - if output_text: - finish_reason = self._extract_finish_reason(result) - self._add_message_event( - span, - role="assistant", - content=output_text, - conversation_id=conversation_id, - finish_reason=finish_reason, - ) - - # Record metrics using new dedicated method - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.OK) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - return result - - def trace_responses_stream(self, function, *args, **kwargs): - """Trace synchronous responses.stream calls.""" - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # No tracing, just call the function - return function(*args, **kwargs) - - # For responses.stream(), always wrap the ResponseStreamManager - try: - result = function(*args, **kwargs) - # Detect if it's async or sync stream manager by checking for __aenter__ - if hasattr(result, "__aenter__"): - # Async stream manager - result = self._wrap_async_response_stream_manager( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - else: - # Sync stream manager - result = self._wrap_response_stream_manager( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - - def trace_responses_stream_async(self, function, *args, **kwargs): - """Trace asynchronous responses.stream calls.""" - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # No tracing, just call the function (don't await - it returns async context manager) - return function(*args, **kwargs) - - # For responses.stream(), always wrap the AsyncResponseStreamManager - # Note: stream() itself is not async, it returns an AsyncResponseStreamManager synchronously - try: - result = function(*args, **kwargs) - # Wrap the AsyncResponseStreamManager - result = self._wrap_async_response_stream_manager( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - - def _wrap_streaming_response( - self, - stream, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap a streaming response to trace chunks.""" - conversation_id = self._extract_conversation_id(original_kwargs) - instrumentor = self # Capture the instrumentor instance - - class StreamWrapper: # pylint: disable=too-many-instance-attributes,protected-access - def __init__( - self, - stream_iter, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.stream_iter = stream_iter - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.accumulated_content = [] - self.span_ended = False - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - - # Enhanced properties for sophisticated chunk processing - self.accumulated_output = [] - self.response_id = None - self.response_model = None - self.service_tier = None - self.input_tokens = 0 - self.output_tokens = 0 - self.finish_reason = None # Track finish_reason from streaming chunks - - # Track all output items from streaming events (tool calls, workflow actions, etc.) - # Use (id, type) as key to avoid overwriting when call and output have same ID - self.output_items = {} # Dict[(item_id, item_type), output_item] - self.has_output_items = False - - # Expose response attribute for compatibility with ResponseStreamManager - self.response = getattr(stream_iter, "response", None) or getattr(stream_iter, "_response", None) - - def append_output_content(self, content): - """Append content to accumulated output list.""" - if content: - self.accumulated_output.append(str(content)) - - def set_response_metadata(self, chunk): - """Update response metadata from chunk if not already set.""" - chunk_type = getattr(chunk, "type", None) - - if not self.response_id: - self.response_id = getattr(chunk, "id", None) - if not self.response_model: - self.response_model = getattr(chunk, "model", None) - if not self.service_tier: - self.service_tier = getattr(chunk, "service_tier", None) - - # Extract finish_reason from response.output_item.done events - if chunk_type == "response.output_item.done" and hasattr(chunk, "item"): - item = chunk.item - if hasattr(item, "status") and item.status: - self.finish_reason = item.status - # Also check for direct finish_reason attribute - elif hasattr(chunk, "finish_reason") and chunk.finish_reason: - self.finish_reason = chunk.finish_reason - # Also check for finish_details in output items (Azure AI Agents structure) - elif hasattr(chunk, "output") and chunk.output: - if isinstance(chunk.output, list) and len(chunk.output) > 0: - output_item = chunk.output[0] - if hasattr(output_item, "finish_details") and output_item.finish_details: - if hasattr(output_item.finish_details, "type"): - self.finish_reason = output_item.finish_details.type - - def process_chunk(self, chunk): - """Process chunk to accumulate data and update metadata.""" - # Check for output item events in streaming - chunk_type = getattr(chunk, "type", None) - - # Collect all complete output items from ResponseOutputItemDoneEvent or ResponseOutputItemAddedEvent - # This includes function_call, file_search_tool_call, code_interpreter_tool_call, - # web_search, mcp_call, computer_tool_call, custom_tool_call, workflow_action, and any future types - if (chunk_type in ("response.output_item.done", "response.output_item.added")) and hasattr( - chunk, "item" - ): - item = chunk.item - item_type = getattr(item, "type", None) - - # Collect any output item (tool calls, workflow actions, etc.) - if item_type: - # Use call_id, action_id, or id as the key (workflow actions use action_id) - item_id = ( - getattr(item, "call_id", None) - or getattr(item, "action_id", None) - or getattr(item, "id", None) - ) - if item_id: - # Use (id, type) tuple as key to distinguish call from output - key = (item_id, item_type) - self.output_items[key] = item - self.has_output_items = True - # Items without ID or type are skipped - - # Capture response ID from ResponseCreatedEvent or ResponseCompletedEvent - if chunk_type == "response.created" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - self.response_model = getattr(chunk.response, "model", None) - elif chunk_type == "response.completed" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - if not self.response_model: - self.response_model = getattr(chunk.response, "model", None) - # Extract usage from the completed response - if hasattr(chunk.response, "usage"): - response_usage = chunk.response.usage - if hasattr(response_usage, "input_tokens") and response_usage.input_tokens: - self.input_tokens = response_usage.input_tokens - if hasattr(response_usage, "output_tokens") and response_usage.output_tokens: - self.output_tokens = response_usage.output_tokens - # Also handle standard token field names for compatibility - if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens: - self.input_tokens = response_usage.prompt_tokens - if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens: - self.output_tokens = response_usage.completion_tokens - - # Only append TEXT content from delta events (not function call arguments or other deltas) - # Text deltas can come as: - # 1. response.text.delta - has delta as string - # 2. response.output_item.delta - has delta.text attribute - # Function call arguments come via response.function_call_arguments.delta - has delta as JSON string - # We need to avoid appending function call arguments - if chunk_type and ".delta" in chunk_type and hasattr(chunk, "delta"): - # If it's function_call_arguments.delta, skip it - if "function_call_arguments" not in chunk_type: - # Check if delta is a string (text content) or has .text attribute - if isinstance(chunk.delta, str): - self.append_output_content(chunk.delta) - elif hasattr(chunk.delta, "text"): - self.append_output_content(chunk.delta.text) - - # Always update metadata - self.set_response_metadata(chunk) - - # Handle usage info - usage = getattr(chunk, "usage", None) - if usage: - if hasattr(usage, "input_tokens") and usage.input_tokens: - self.input_tokens += usage.input_tokens - if hasattr(usage, "output_tokens") and usage.output_tokens: - self.output_tokens += usage.output_tokens - # Also handle standard token field names - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self.input_tokens += usage.prompt_tokens - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self.output_tokens += usage.completion_tokens - - def cleanup(self): - """Perform final cleanup when streaming is complete.""" - if not self.span_ended: - duration = time.time() - self.start_time - - # Join all accumulated output content - complete_content = "".join(self.accumulated_output) - - if self.span.span_instance.is_recording: - # Add tool call events if we detected any output items (tool calls, etc.) - if self.has_output_items: - # Create mock response with output items for event generation - # The existing _add_tool_call_events method handles all tool types - mock_response = type( - "Response", - (), - {"output": list(self.output_items.values())}, - )() - self.instrumentor._add_tool_call_events( - self.span, - mock_response, - self.conversation_id, - ) - # Also add workflow action events - self.instrumentor._add_workflow_action_events( - self.span, - mock_response, - self.conversation_id, - ) - - # Only add assistant message event if there's actual text content (not empty/whitespace) - if complete_content and complete_content.strip(): - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=complete_content, - conversation_id=self.conversation_id, - finish_reason=self.finish_reason, - ) - - # Set final span attributes using accumulated metadata - if self.response_id: - self.instrumentor._set_span_attribute_safe(self.span, GEN_AI_RESPONSE_ID, self.response_id) - if self.response_model: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_RESPONSE_MODEL, self.response_model - ) - - if self.service_tier: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, - self.service_tier, - ) - - # Set token usage span attributes - if self.input_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens - ) - if self.output_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_USAGE_OUTPUT_TOKENS, - self.output_tokens, - ) - - # Record metrics using accumulated data - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - - # Create mock result object with accumulated data for metrics - class MockResult: - def __init__( - self, - response_id, - response_model, - service_tier, - input_tokens, - output_tokens, - ): - self.id = response_id - self.model = response_model - self.service_tier = service_tier - if input_tokens > 0 or output_tokens > 0: - self.usage = type( - "Usage", - (), - { - "input_tokens": input_tokens, - "output_tokens": output_tokens, - "prompt_tokens": input_tokens, - "completion_tokens": output_tokens, - }, - )() - - mock_result = MockResult( - self.response_id, - self.response_model, - self.service_tier, - self.input_tokens, - self.output_tokens, - ) - - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=mock_result, - span_attributes=span_attributes, - ) - - # End span with proper status - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - def __iter__(self): - # Start streaming iteration - return self - - def __next__(self): - try: - chunk = next(self.stream_iter) - # Process chunk to accumulate data and maintain API compatibility - self.process_chunk(chunk) - # Also maintain backward compatibility with old accumulated_content - if hasattr(chunk, "output") and chunk.output: - self.accumulated_content.append(str(chunk.output)) - elif hasattr(chunk, "delta") and isinstance(chunk.delta, str): - self.accumulated_content.append(chunk.delta) - return chunk - except StopIteration: - # Stream is finished, perform cleanup - self.cleanup() - raise - except Exception as e: - # Error occurred, record metrics and set error status - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - self.span_ended = True - raise - - def _finalize_span(self): - """Finalize the span with accumulated content and end it.""" - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - if self.span.span_instance.is_recording: - # Note: For streaming responses, response metadata like tokens, finish_reasons - # are typically not available in individual chunks, so we focus on content. - - if self.accumulated_content: - full_content = "".join(self.accumulated_content) - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=full_content, - conversation_id=self.conversation_id, - ) - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - try: - self.cleanup() - except Exception: - pass # Don't let cleanup exceptions mask the original exception - return False - - def get_final_response(self): - """Proxy method to access the underlying stream's get_final_response if available.""" - if hasattr(self.stream_iter, "get_final_response"): - return self.stream_iter.get_final_response() - raise AttributeError("Underlying stream does not have 'get_final_response' method") - - return StreamWrapper( - stream, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ) - - def _wrap_response_stream_manager( - self, - stream_manager, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap a ResponseStreamManager to trace the stream when it's entered.""" - conversation_id = self._extract_conversation_id(original_kwargs) - instrumentor = self - - class ResponseStreamManagerWrapper: - """Wrapper for ResponseStreamManager that adds tracing to the underlying stream.""" - - def __init__( - self, - manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.manager = manager - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - self.wrapped_stream = None - - def __enter__(self): - # Enter the underlying ResponseStreamManager to get the ResponseStream - raw_stream = self.manager.__enter__() - # Wrap the ResponseStream with our tracing wrapper - self.wrapped_stream = self.instrumentor._wrap_streaming_response( - raw_stream, - self.span, - ({"conversation": self.conversation_id} if self.conversation_id else {}), - self.start_time, - self.operation_name, - self.server_address, - self.port, - self.model, - ) - return self.wrapped_stream - - def __exit__(self, exc_type, exc_val, exc_tb): - # Exit the underlying ResponseStreamManager - result = self.manager.__exit__(exc_type, exc_val, exc_tb) - return result - - return ResponseStreamManagerWrapper( - stream_manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ) - - def _wrap_async_streaming_response( - self, - stream, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap an async streaming response to trace chunks.""" - conversation_id = self._extract_conversation_id(original_kwargs) - - class AsyncStreamWrapper: # pylint: disable=too-many-instance-attributes,protected-access - def __init__( - self, - stream_async_iter, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.stream_async_iter = stream_async_iter - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.accumulated_content = [] - self.span_ended = False - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - - # Enhanced properties for sophisticated chunk processing - self.accumulated_output = [] - self.response_id = None - self.response_model = None - self.service_tier = None - self.input_tokens = 0 - self.output_tokens = 0 - self.finish_reason = None # Track finish_reason from streaming chunks - - # Track all output items from streaming events (tool calls, workflow actions, etc.) - # Use (id, type) as key to avoid overwriting when call and output have same ID - self.output_items = {} # Dict[(item_id, item_type), output_item] - self.has_output_items = False - - # Expose response attribute for compatibility with AsyncResponseStreamManager - self.response = getattr(stream_async_iter, "response", None) or getattr( - stream_async_iter, "_response", None - ) - - def append_output_content(self, content): - """Append content to accumulated output list.""" - if content: - self.accumulated_output.append(str(content)) - - def set_response_metadata(self, chunk): - """Update response metadata from chunk if not already set.""" - chunk_type = getattr(chunk, "type", None) - - if not self.response_id: - self.response_id = getattr(chunk, "id", None) - if not self.response_model: - self.response_model = getattr(chunk, "model", None) - if not self.service_tier: - self.service_tier = getattr(chunk, "service_tier", None) - - # Extract finish_reason from response.output_item.done events - if chunk_type == "response.output_item.done" and hasattr(chunk, "item"): - item = chunk.item - if hasattr(item, "status") and item.status: - self.finish_reason = item.status - # Also check for direct finish_reason attribute - elif hasattr(chunk, "finish_reason") and chunk.finish_reason: - self.finish_reason = chunk.finish_reason - # Also check for finish_details in output items (Azure AI Agents structure) - elif hasattr(chunk, "output") and chunk.output: - if isinstance(chunk.output, list) and len(chunk.output) > 0: - output_item = chunk.output[0] - if hasattr(output_item, "finish_details") and output_item.finish_details: - if hasattr(output_item.finish_details, "type"): - self.finish_reason = output_item.finish_details.type - - def process_chunk(self, chunk): - """Process chunk to accumulate data and update metadata.""" - # Check for output item events in streaming - chunk_type = getattr(chunk, "type", None) - - # Collect all complete output items from ResponseOutputItemDoneEvent or ResponseOutputItemAddedEvent - # This includes function_call, file_search_tool_call, code_interpreter_tool_call, - # web_search, mcp_call, computer_tool_call, custom_tool_call, workflow_action, and any future types - if (chunk_type in ("response.output_item.done", "response.output_item.added")) and hasattr( - chunk, "item" - ): - item = chunk.item - item_type = getattr(item, "type", None) - - # Collect any output item (tool calls, workflow actions, etc.) - if item_type: - # Use call_id, action_id, or id as the key (workflow actions use action_id) - item_id = ( - getattr(item, "call_id", None) - or getattr(item, "action_id", None) - or getattr(item, "id", None) - ) - if item_id: - # Use (id, type) tuple as key to distinguish call from output - self.output_items[(item_id, item_type)] = item - self.has_output_items = True - # Items without ID or type are skipped - - # Capture response ID from ResponseCreatedEvent or ResponseCompletedEvent - if chunk_type == "response.created" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - self.response_model = getattr(chunk.response, "model", None) - elif chunk_type == "response.completed" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - if not self.response_model: - self.response_model = getattr(chunk.response, "model", None) - # Extract usage from the completed response - if hasattr(chunk.response, "usage"): - response_usage = chunk.response.usage - if hasattr(response_usage, "input_tokens") and response_usage.input_tokens: - self.input_tokens = response_usage.input_tokens - if hasattr(response_usage, "output_tokens") and response_usage.output_tokens: - self.output_tokens = response_usage.output_tokens - # Also handle standard token field names for compatibility - if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens: - self.input_tokens = response_usage.prompt_tokens - if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens: - self.output_tokens = response_usage.completion_tokens - - # Only append TEXT content from delta events (not function call arguments or other deltas) - # Text deltas can come as: - # 1. response.text.delta - has delta as string - # 2. response.output_item.delta - has delta.text attribute - # Function call arguments come via response.function_call_arguments.delta - has delta as JSON string - # We need to avoid appending function call arguments - if chunk_type and ".delta" in chunk_type and hasattr(chunk, "delta"): - # If it's function_call_arguments.delta, skip it - if "function_call_arguments" not in chunk_type: - # Check if delta is a string (text content) or has .text attribute - if isinstance(chunk.delta, str): - self.append_output_content(chunk.delta) - elif hasattr(chunk.delta, "text"): - self.append_output_content(chunk.delta.text) - - # Always update metadata - self.set_response_metadata(chunk) - - # Handle usage info - usage = getattr(chunk, "usage", None) - if usage: - if hasattr(usage, "input_tokens") and usage.input_tokens: - self.input_tokens += usage.input_tokens - if hasattr(usage, "output_tokens") and usage.output_tokens: - self.output_tokens += usage.output_tokens - # Also handle standard token field names - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self.input_tokens += usage.prompt_tokens - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self.output_tokens += usage.completion_tokens - - def cleanup(self): - """Perform final cleanup when streaming is complete.""" - if not self.span_ended: - duration = time.time() - self.start_time - - # Join all accumulated output content - complete_content = "".join(self.accumulated_output) - - if self.span.span_instance.is_recording: - # Add tool call events if we detected any output items (tool calls, etc.) - if self.has_output_items: - # Create mock response with output items for event generation - # The existing _add_tool_call_events method handles all tool types - mock_response = type( - "Response", - (), - {"output": list(self.output_items.values())}, - )() - self.instrumentor._add_tool_call_events( - self.span, - mock_response, - self.conversation_id, - ) - # Also add workflow action events - self.instrumentor._add_workflow_action_events( - self.span, - mock_response, - self.conversation_id, - ) - - # Only add assistant message event if there's actual text content (not empty/whitespace) - if complete_content and complete_content.strip(): - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=complete_content, - conversation_id=self.conversation_id, - finish_reason=self.finish_reason, - ) - - # Set final span attributes using accumulated metadata - if self.response_id: - self.instrumentor._set_span_attribute_safe(self.span, GEN_AI_RESPONSE_ID, self.response_id) - if self.response_model: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_RESPONSE_MODEL, self.response_model - ) - - if self.service_tier: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, - self.service_tier, - ) - - # Set token usage span attributes - if self.input_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens - ) - if self.output_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_USAGE_OUTPUT_TOKENS, - self.output_tokens, - ) - - # Record metrics using accumulated data - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - - # Create mock result object with accumulated data for metrics - class MockResult: - def __init__( - self, - response_id, - response_model, - service_tier, - input_tokens, - output_tokens, - ): - self.id = response_id - self.model = response_model - self.service_tier = service_tier - if input_tokens > 0 or output_tokens > 0: - self.usage = type( - "Usage", - (), - { - "input_tokens": input_tokens, - "output_tokens": output_tokens, - "prompt_tokens": input_tokens, - "completion_tokens": output_tokens, - }, - )() - - mock_result = MockResult( - self.response_id, - self.response_model, - self.service_tier, - self.input_tokens, - self.output_tokens, - ) - - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=mock_result, - span_attributes=span_attributes, - ) - - # End span with proper status - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - def __aiter__(self): - return self - - async def __anext__(self): - try: - chunk = await self.stream_async_iter.__anext__() - # Process chunk to accumulate data and maintain API compatibility - self.process_chunk(chunk) - # Also maintain backward compatibility with old accumulated_content - if hasattr(chunk, "output") and chunk.output: - self.accumulated_content.append(str(chunk.output)) - elif hasattr(chunk, "delta") and isinstance(chunk.delta, str): - self.accumulated_content.append(chunk.delta) - return chunk - except StopAsyncIteration: - # Stream is finished, perform cleanup - self.cleanup() - raise - except Exception as e: - # Error occurred, record metrics and set error status - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - self.span_ended = True - raise - - def _finalize_span(self): - """Finalize the span with accumulated content and end it.""" - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - if self.span.span_instance.is_recording: - # Note: For streaming responses, response metadata like tokens, finish_reasons - # are typically not available in individual chunks, so we focus on content. - - if self.accumulated_content: - full_content = "".join(self.accumulated_content) - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=full_content, - conversation_id=self.conversation_id, - ) - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - try: - self.cleanup() - except Exception: - pass # Don't let cleanup exceptions mask the original exception - return False - - async def get_final_response(self): - """Proxy method to access the underlying stream's get_final_response if available.""" - if hasattr(self.stream_async_iter, "get_final_response"): - result = self.stream_async_iter.get_final_response() - # If it's a coroutine, await it - if hasattr(result, "__await__"): - return await result - return result - raise AttributeError("Underlying stream does not have 'get_final_response' method") - - return AsyncStreamWrapper( - stream, - span, - conversation_id, - self, - start_time, - operation_name, - server_address, - port, - model, - ) - - def _wrap_async_response_stream_manager( - self, - stream_manager, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap an AsyncResponseStreamManager to trace the stream when it's entered.""" - conversation_id = self._extract_conversation_id(original_kwargs) - instrumentor = self - - class AsyncResponseStreamManagerWrapper: - """Wrapper for AsyncResponseStreamManager that adds tracing to the underlying stream.""" - - def __init__( - self, - manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.manager = manager - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - self.wrapped_stream = None - - async def __aenter__(self): - # Enter the underlying AsyncResponseStreamManager to get the AsyncResponseStream - raw_stream = await self.manager.__aenter__() - # Wrap the AsyncResponseStream with our tracing wrapper - self.wrapped_stream = self.instrumentor._wrap_async_streaming_response( - raw_stream, - self.span, - ({"conversation": self.conversation_id} if self.conversation_id else {}), - self.start_time, - self.operation_name, - self.server_address, - self.port, - self.model, - ) - return self.wrapped_stream - - async def __aexit__(self, exc_type, exc_val, exc_tb): - # Exit the underlying AsyncResponseStreamManager - result = await self.manager.__aexit__(exc_type, exc_val, exc_tb) - return result - - return AsyncResponseStreamManagerWrapper( - stream_manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ) - - def start_create_conversation_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - ) -> "Optional[AbstractSpan]": - """Start a span for create conversation API call.""" - span = start_span( - operation_name=OperationName.CREATE_CONVERSATION, - server_address=server_address, - port=port, - span_name=OperationName.CREATE_CONVERSATION.value, - gen_ai_provider=RESPONSES_PROVIDER, - ) - - if span and span.span_instance.is_recording: - self._set_span_attribute_safe(span, GEN_AI_OPERATION_NAME, OperationName.CREATE_CONVERSATION.value) - - return span - - def _create_conversations_span_from_parameters(self, *args, **kwargs): # pylint: disable=unused-argument - """Extract parameters and create span for conversations API tracing.""" - # Extract client from args (first argument) - client = args[0] if args else None - server_address, port = self._extract_server_info_from_client(client) - - # Create and return the span - return self.start_create_conversation_span( - server_address=server_address, - port=port, - ) - - def trace_conversations_create(self, function, *args, **kwargs): - """Trace synchronous conversations.create calls.""" - span = self._create_conversations_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "create_conversation" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - with span: - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set conversation attributes - self._extract_conversation_attributes(span, result) - - # Record metrics using new dedicated method - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - - async def trace_conversations_create_async(self, function, *args, **kwargs): - """Trace asynchronous conversations.create calls.""" - span = self._create_conversations_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "create_conversation" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - with span: - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set conversation attributes - self._extract_conversation_attributes(span, result) - - # Record metrics using new dedicated method - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - - def start_list_conversation_items_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - conversation_id: Optional[str] = None, - ) -> "Optional[AbstractSpan]": - """Start a span for list conversation items API call.""" - span = start_span( - operation_name=OperationName.LIST_CONVERSATION_ITEMS, - server_address=server_address, - port=port, - span_name=OperationName.LIST_CONVERSATION_ITEMS.value, - gen_ai_provider=RESPONSES_PROVIDER, - ) - - if span and span.span_instance.is_recording: - # Set operation name attribute (start_span doesn't set this automatically) - self._set_attributes( - span, - (GEN_AI_OPERATION_NAME, OperationName.LIST_CONVERSATION_ITEMS.value), - ) - - # Set conversation-specific attributes that start_span doesn't handle - # Note: server_address is already set by start_span, so we don't need to set it again - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - - return span - - def _add_conversation_item_event( # pylint: disable=too-many-branches,too-many-locals - self, - span: "AbstractSpan", - item: Any, - ) -> None: - """Add a conversation item event to the span.""" - if not span or not span.span_instance.is_recording: - return - - # Extract basic item information - item_id = getattr(item, "id", None) - item_type = getattr(item, "type", "unknown") - role = getattr(item, "role", None) - - # Create event body - format depends on item type - event_body: List[Dict[str, Any]] = [] - - # Declare tool_call variable with type for use across branches - tool_call: Dict[str, Any] - - # Handle different item types - if item_type == "function_call_output": - # Function tool output - use optimized content format - role = "tool" # Override role for tool outputs - - tool_output: Dict[str, Any] = { - "type": item_type, - } - - # Add call_id as "id" - always include for correlation - if hasattr(item, "call_id"): - tool_output["id"] = item.call_id - elif hasattr(item, "id"): - tool_output["id"] = item.id - - # Add output field only if content recording is enabled - if _trace_responses_content: - if hasattr(item, "output"): - output_value = item.output - if isinstance(output_value, str): - try: - tool_output["output"] = json.loads(output_value) - except (json.JSONDecodeError, TypeError): - tool_output["output"] = output_value - else: - tool_output["output"] = output_value - - # Always include role and parts with type/id, only output when content recording enabled - event_body = [ - { - "role": role, - "parts": [{"type": "tool_call_output", "content": tool_output}], - } - ] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "function_call": - # Function tool call - use optimized content format - role = "assistant" # Override role for function calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include function details if content recording is enabled - if _trace_responses_content: - # Add function details - if hasattr(item, "name"): - function_details: Dict[str, Any] = { - "name": item.name, - } - if hasattr(item, "arguments"): - # Parse arguments if it's a JSON string - args_value = item.arguments - if isinstance(args_value, str): - try: - function_details["arguments"] = json.loads(args_value) - except (json.JSONDecodeError, TypeError): - function_details["arguments"] = args_value - else: - function_details["arguments"] = args_value - - tool_call["function"] = function_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "file_search_call": - # File search tool call - role = "assistant" # Override role for file search calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include file search details if content recording is enabled - if _trace_responses_content: - # Add file search details - file_search_details: Dict[str, Any] = {} - - if hasattr(item, "queries") and item.queries: - file_search_details["queries"] = item.queries - - if hasattr(item, "status"): - file_search_details["status"] = item.status - - if hasattr(item, "results") and item.results: - file_search_details["results"] = [ - { - "file_id": getattr(result, "file_id", None), - "file_name": getattr(result, "file_name", None), - "score": getattr(result, "score", None), - } - for result in item.results - ] - - if file_search_details: - tool_call["file_search"] = file_search_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "code_interpreter_call": - # Code interpreter tool call - role = "assistant" # Override role for code interpreter calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include code interpreter details if content recording is enabled - if _trace_responses_content: - # Add code interpreter details - code_interpreter_details: Dict[str, Any] = {} - - if hasattr(item, "code") and item.code: - code_interpreter_details["code"] = item.code - - if hasattr(item, "status"): - code_interpreter_details["status"] = item.status - - if hasattr(item, "outputs") and item.outputs: - outputs_list = [] - for output in item.outputs: - output_type = getattr(output, "type", None) - if output_type == "logs": - outputs_list.append({"type": "logs", "logs": getattr(output, "logs", None)}) - elif output_type == "image": - # Use consistent "content" field for image data - outputs_list.append( - { - "type": "image", - "content": { - "file_id": getattr( - getattr(output, "image", None), - "file_id", - None, - ) - }, - } - ) - if outputs_list: - code_interpreter_details["outputs"] = outputs_list - - if code_interpreter_details: - tool_call["code_interpreter"] = code_interpreter_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "web_search_call": - # Web search tool call - role = "assistant" # Override role for web search calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include web search details if content recording is enabled - if _trace_responses_content: - # Add web search details - web_search_details: Dict[str, Any] = {} - - if hasattr(item, "status"): - web_search_details["status"] = item.status - - if hasattr(item, "action") and item.action: - action_type = getattr(item.action, "type", None) - web_search_details["action_type"] = action_type - - if action_type == "search" and hasattr(item.action, "query"): - web_search_details["query"] = item.action.query - elif action_type == "open_page" and hasattr(item.action, "url"): - web_search_details["url"] = item.action.url - elif action_type == "find" and hasattr(item.action, "query"): - web_search_details["find_query"] = item.action.query - - if web_search_details: - tool_call["web_search"] = web_search_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "azure_ai_search_call": - # Azure AI Search tool call - role = "assistant" # Override role for Azure AI Search calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include Azure AI Search details if content recording is enabled - if _trace_responses_content: - # Add Azure AI Search details - azure_ai_search_details: Dict[str, Any] = {} - - if hasattr(item, "status"): - azure_ai_search_details["status"] = item.status - - if hasattr(item, "input"): - azure_ai_search_details["input"] = item.input - - if hasattr(item, "results") and item.results: - azure_ai_search_details["results"] = [] - for result in item.results: - result_data = {} - if hasattr(result, "title"): - result_data["title"] = result.title - if hasattr(result, "url"): - result_data["url"] = result.url - if hasattr(result, "content"): - result_data["content"] = result.content - if result_data: - azure_ai_search_details["results"].append(result_data) - - if azure_ai_search_details: - tool_call["azure_ai_search"] = azure_ai_search_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "image_generation_call": - # Image generation tool call - role = "assistant" # Override role for image generation calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include image generation details if content recording is enabled - if _trace_responses_content: - # Add image generation details - image_gen_details: Dict[str, Any] = {} - - if hasattr(item, "prompt"): - image_gen_details["prompt"] = item.prompt - - if hasattr(item, "quality"): - image_gen_details["quality"] = item.quality - - if hasattr(item, "size"): - image_gen_details["size"] = item.size - - if hasattr(item, "style"): - image_gen_details["style"] = item.style - - # Include the result (image data) only if binary data tracing is enabled - if _trace_binary_data and hasattr(item, "result") and item.result: - image_gen_details["result"] = item.result - - if image_gen_details: - tool_call["image_generation"] = image_gen_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "remote_function_call": - # Remote function call (like Bing Custom Search call) - role = "assistant" # Override role for remote function calls - - # Check if there's a more specific type in name field (e.g., "bing_custom_search_preview_call") - specific_type = None - if hasattr(item, "name") and item.name: - # Use the API type directly without transformation - specific_type = item.name - - tool_call = { - "type": specific_type if specific_type else item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "id"): - tool_call["id"] = item.id - elif hasattr(item, "call_id"): - tool_call["id"] = item.call_id - # Check model_extra for call_id - elif hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - if "call_id" in item.model_extra: - tool_call["id"] = item.model_extra["call_id"] - - # Only include tool details if content recording is enabled - if _trace_responses_content: - # Extract data from model_extra if available (Pydantic v2 style) - if hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - for key, value in item.model_extra.items(): - # Skip already captured fields, redundant fields (name, label), internal fields (partition_key), and empty/None values - if ( - key not in ["type", "id", "call_id", "name", "label", "partition_key"] - and value is not None - and value != "" - ): - tool_call[key] = value - - # Also try as_dict if available - if hasattr(item, "as_dict"): - try: - tool_dict = item.as_dict() - # Extract relevant fields (exclude already captured ones and empty/None values) - for key, value in tool_dict.items(): - if key not in [ - "type", - "id", - "call_id", - "name", - "label", - "role", - "content", - ]: - # Skip empty strings and None values - if value is not None and value != "": - # Don't overwrite if already exists - if key not in tool_call: - tool_call[key] = value - except Exception as e: - logger.debug(f"Failed to extract data from as_dict: {e}") - - # Fallback: try common fields directly (skip if empty and skip redundant name/label) - for field in [ - "input", - "arguments", - "status", - "error", - "search_query", - "query", - ]: - if hasattr(item, field): - try: - value = getattr(item, field) - if value is not None and value != "": - # If not already in tool_call, add it - if field not in tool_call: - tool_call[field] = value - except Exception: - pass - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "remote_function_call_output": - # Remote function call output (like Bing Custom Search output) - role = "tool" # Tool outputs use role "tool" - - # Check if there's a more specific type in name field (e.g., "bing_custom_search_preview_call_output") - specific_type = None - if hasattr(item, "name") and item.name: - # Use the API type directly without transformation - specific_type = item.name - - tool_output = { - "type": specific_type if specific_type else item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "id"): - tool_output["id"] = item.id - elif hasattr(item, "call_id"): - tool_output["id"] = item.call_id - # Check model_extra for call_id - elif hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - if "call_id" in item.model_extra: - tool_output["id"] = item.model_extra["call_id"] - - # Only include tool details if content recording is enabled - if _trace_responses_content: - # Extract data from model_extra if available (Pydantic v2 style) - if hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - for key, value in item.model_extra.items(): - # Skip already captured fields, redundant fields (name, label), internal fields (partition_key), and empty/None values - if ( - key not in ["type", "id", "call_id", "name", "label", "partition_key"] - and value is not None - and value != "" - ): - tool_output[key] = value - - # Also try as_dict if available - if hasattr(item, "as_dict"): - try: - tool_dict = item.as_dict() - # Extract relevant fields (exclude already captured ones and empty/None values) - for key, value in tool_dict.items(): - if key not in [ - "type", - "id", - "call_id", - "name", - "label", - "role", - "content", - ]: - # Skip empty strings and None values - if value is not None and value != "": - # Don't overwrite if already exists - if key not in tool_output: - tool_output[key] = value - except Exception as e: - logger.debug(f"Failed to extract data from as_dict: {e}") - - # Fallback: try common fields directly (skip if empty and skip redundant name/label) - for field in [ - "input", - "output", - "results", - "status", - "error", - "search_query", - "query", - ]: - if hasattr(item, field): - try: - value = getattr(item, field) - if value is not None and value != "": - # If not already in tool_output, add it - if field not in tool_output: - tool_output[field] = value - except Exception: - pass - - # Tool outputs use tool_call_output type in parts - event_body = [{"role": role, "parts": [{"type": "tool_call_output", "content": tool_output}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "workflow_action": - # Workflow action item - include workflow execution details - role = "workflow" - - # Extract workflow action attributes - status = getattr(item, "status", None) - - # Build workflow action details object - workflow_details: Dict[str, Any] = {} - - if status: - workflow_details["status"] = status - - # Only include action_id and previous_action_id when content recording is enabled - if _trace_responses_content: - action_id = getattr(item, "action_id", None) - previous_action_id = getattr(item, "previous_action_id", None) - - if action_id: - workflow_details["action_id"] = action_id - if previous_action_id: - workflow_details["previous_action_id"] = previous_action_id - - # Wrap in parts array for semantic convention compliance - parts: List[Dict[str, Any]] = [{"type": "workflow_action", "content": workflow_details}] - event_body = [{"role": role, "parts": parts}] - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "message": - # Regular message - use content format for consistency - parts = [] - - # Always inspect content to determine types, regardless of recording setting - if hasattr(item, "content") and item.content: - for content_item in item.content: - content_type = getattr(content_item, "type", None) - - if content_type in ("input_text", "output_text", "text"): - if _trace_responses_content and hasattr(content_item, "text"): - # Include actual text content when recording is enabled - parts.append({"type": "text", "content": content_item.text}) - else: - # Type-only when recording is disabled - parts.append({"type": "text"}) - elif content_type == "input_image": - # Handle image content - image_part = {"type": "image"} - # Include image data if binary data tracing is enabled - # Note: The API typically doesn't return image_url in conversation items list, - # only in the original responses.create call - if _trace_binary_data: - image_url = getattr(content_item, "image_url", None) - if image_url: - # Use consistent format: content field directly contains the URL - image_part["content"] = image_url - parts.append(image_part) - elif content_type == "input_file": - # Handle file content - file_part: Dict[str, Any] = {"type": "file"} - file_content_dict: Dict[str, Any] = {} - filename = getattr(content_item, "filename", None) - if filename: - file_content_dict["filename"] = filename - file_id = getattr(content_item, "file_id", None) - if file_id: - file_content_dict["file_id"] = file_id - # Include file data if binary data tracing is enabled - if _trace_binary_data: - file_data = getattr(content_item, "file_data", None) - if file_data: - file_content_dict["file_data"] = file_data - if file_content_dict: - file_part["content"] = file_content_dict - parts.append(file_part) - - # Always create event_body with role and parts - role_obj: Dict[str, Any] = {"role": role} - if parts: - role_obj["parts"] = parts - - event_body = [role_obj] - - # Use conversation item event for all message items during listing - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - elif item_type and item_type.startswith("mcp"): - # MCP-specific item types (mcp_approval_request, mcp_list_tools, mcp_call, etc.) - # Determine role based on whether it's a response (user) or request/call (assistant) - if "response" in item_type: - # MCP responses (e.g., mcp_approval_response) are user inputs - mcp_role = "user" - else: - # MCP requests/calls (e.g., mcp_approval_request, mcp_list_tools, mcp_call) are assistant-initiated - mcp_role = "assistant" - - # Create structured event body - mcp_tool_call: Dict[str, Any] = { - "type": item_type, - } - - # Always include ID if available - if hasattr(item, "id"): - mcp_tool_call["id"] = item.id - elif hasattr(item, "call_id"): - mcp_tool_call["id"] = item.call_id - elif hasattr(item, "approval_request_id"): - # For approval responses, use the request ID - mcp_tool_call["id"] = item.approval_request_id - - # Only include additional details if content recording is enabled - if _trace_responses_content: - # Try to capture common MCP fields - for field in [ - "name", - "server_label", - "arguments", - "approval_request_id", - "approve", - "status", - ]: - if hasattr(item, field): - value = getattr(item, field) - if value is not None: - mcp_tool_call[field] = value - - # Wrap in parts array with appropriate role - event_body = [{"role": mcp_role, "parts": [{"type": "mcp", "content": mcp_tool_call}]}] - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - else: - # Unknown item type - create minimal event body with role if available - # This handles MCP tools and other future item types - else_role_obj: Dict[str, Any] = {} - if role: - else_role_obj["role"] = role - event_body = [else_role_obj] if else_role_obj else [] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - # Create event attributes - event_attributes = { - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - GEN_AI_CONVERSATION_ITEM_ID: item_id, - } - - # Commented out - message_role is now included in the event content instead - # # Add role attribute if present - # if role is not None: - # event_attributes[GEN_AI_CONVERSATION_ITEM_ROLE] = role - - # Use JSON format for event content (consistent with responses.create) - event_attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False) - - span.span_instance.add_event(name=event_name, attributes=event_attributes) - - def _wrap_conversation_items_list( - self, - result, - span: Optional["AbstractSpan"], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - ): - """Wrap the conversation items list result to add events for each item.""" - - class ItemsWrapper: - def __init__( - self, - items_result, - span, - instrumentor, - start_time, - operation_name, - server_address, - port, - ): - self.items_result = items_result - self.span = span - self.instrumentor = instrumentor - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - - def __iter__(self): - # For synchronous iteration - try: - for item in self.items_result: - if self.span: - self.instrumentor._add_conversation_item_event(self.span, item) - yield item - - # Record metrics when iteration is complete - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - # End span when iteration is complete - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - except Exception as e: - # Record metrics for error case - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - raise - - async def __aiter__(self): - # For asynchronous iteration - try: - async for item in self.items_result: - if self.span: - self.instrumentor._add_conversation_item_event(self.span, item) - yield item - - # Record metrics when iteration is complete - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - # End span when iteration is complete - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - except Exception as e: - # Record metrics for error case - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - raise - - def __getattr__(self, name): - # Delegate other attributes to the original result - return getattr(self.items_result, name) - - return ItemsWrapper(result, span, self, start_time, operation_name, server_address, port) - - def _create_list_conversation_items_span_from_parameters(self, *args, **kwargs): - """Extract parameters and create span for list conversation items API tracing.""" - # Extract client from args (first argument) - client = args[0] if args else None - server_address, port = self._extract_server_info_from_client(client) - - # Extract conversation_id from kwargs - conversation_id = kwargs.get("conversation_id") - - return self.start_list_conversation_items_span( - server_address=server_address, - port=port, - conversation_id=conversation_id, - ) - - def trace_list_conversation_items(self, function, *args, **kwargs): - """Trace synchronous conversations.items.list calls.""" - span = self._create_list_conversation_items_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "list_conversation_items" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = function(*args, **kwargs) - # For list operations, we can't measure duration until iteration is complete - # So we'll record metrics in the wrapper or during iteration - return self._wrap_conversation_items_list( - result, None, start_time, operation_name, server_address, port - ) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Don't use context manager since we need the span to stay open during iteration - try: - result = function(*args, **kwargs) - - # Extract and set conversation items attributes - self._extract_conversation_items_attributes(span, result, args, kwargs) - - # Wrap the result to add events during iteration and handle span ending - wrapped_result = self._wrap_conversation_items_list( - result, span, start_time, operation_name, server_address, port - ) - - return wrapped_result - - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.ERROR, str(e)) - span.span_instance.record_exception(e) - span.span_instance.end() - raise - - async def trace_list_conversation_items_async(self, function, *args, **kwargs): - """Trace asynchronous conversations.items.list calls.""" - span = self._create_list_conversation_items_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "list_conversation_items" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = await function(*args, **kwargs) - # For list operations, we can't measure duration until iteration is complete - # So we'll record metrics in the wrapper or during iteration - return self._wrap_conversation_items_list( - result, None, start_time, operation_name, server_address, port - ) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Don't use context manager since we need the span to stay open during iteration - try: - result = await function(*args, **kwargs) - - # Extract and set conversation items attributes - self._extract_conversation_items_attributes(span, result, args, kwargs) - - # Wrap the result to add events during iteration and handle span ending - wrapped_result = self._wrap_conversation_items_list( - result, span, start_time, operation_name, server_address, port - ) - - return wrapped_result - - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.ERROR, str(e)) - span.span_instance.record_exception(e) - span.span_instance.end() - raise - - def _trace_sync_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.RESPONSES, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to a synchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.RESPONSES. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - def inner(*args, **kwargs): - if _name == "create" and _trace_type == TraceType.RESPONSES: - return self.trace_responses_create(function, *args, **kwargs) - if _name == "stream" and _trace_type == TraceType.RESPONSES: - return self.trace_responses_stream(function, *args, **kwargs) - if _name == "create" and _trace_type == TraceType.CONVERSATIONS: - return self.trace_conversations_create(function, *args, **kwargs) - if _name == "list" and _trace_type == TraceType.CONVERSATIONS: - return self.trace_list_conversation_items(function, *args, **kwargs) - - return function(*args, **kwargs) - - return inner - - def _trace_async_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.RESPONSES, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to an asynchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.RESPONSES. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - async def inner(*args, **kwargs): - if _name == "create" and _trace_type == TraceType.RESPONSES: - return await self.trace_responses_create_async(function, *args, **kwargs) - if _name == "stream" and _trace_type == TraceType.RESPONSES: - # stream() is not async, just returns async context manager, so don't await - return self.trace_responses_stream_async(function, *args, **kwargs) - if _name == "create" and _trace_type == TraceType.CONVERSATIONS: - return await self.trace_conversations_create_async(function, *args, **kwargs) - if _name == "list" and _trace_type == TraceType.CONVERSATIONS: - return await self.trace_list_conversation_items_async(function, *args, **kwargs) - - return await function(*args, **kwargs) - - return inner - - def _inject_async(self, f, _trace_type, _name): - wrapper_fun = self._trace_async_function(f, _trace_type=_trace_type, _name=_name) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _inject_sync(self, f, _trace_type, _name): - wrapper_fun = self._trace_sync_function(f, _trace_type=_trace_type, _name=_name) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _responses_apis(self): - sync_apis = [] - async_apis = [] - - try: - import openai.resources.responses as responses_module - - if hasattr(responses_module, "Responses"): - sync_apis.append( - ( - responses_module.Responses, - "create", - TraceType.RESPONSES, - self._inject_sync, - "create", - ) - ) - # Add stream method - sync_apis.append( - ( - responses_module.Responses, - "stream", - TraceType.RESPONSES, - self._inject_sync, - "stream", - ) - ) - except ImportError: - pass - - try: - import openai.resources.responses as responses_module - - if hasattr(responses_module, "AsyncResponses"): - async_apis.append( - ( - responses_module.AsyncResponses, - "create", - TraceType.RESPONSES, - self._inject_async, - "create", - ) - ) - # Add stream method - note: stream() is not async, just returns async context manager - # So we use _inject_sync even though it's on AsyncResponses - sync_apis.append( - ( - responses_module.AsyncResponses, - "stream", - TraceType.RESPONSES, - self._inject_sync, - "stream", - ) - ) - except ImportError: - pass - - return sync_apis, async_apis - - def _conversations_apis(self): - sync_apis = [] - async_apis = [] - - try: - from openai.resources.conversations.conversations import Conversations - - sync_apis.append( - ( - Conversations, - "create", - TraceType.CONVERSATIONS, - self._inject_sync, - "create", - ) - ) - except ImportError: - pass - - try: - from openai.resources.conversations.conversations import AsyncConversations - - async_apis.append( - ( - AsyncConversations, - "create", - TraceType.CONVERSATIONS, - self._inject_async, - "create", - ) - ) - except ImportError: - pass - - # Add conversation items APIs - try: - from openai.resources.conversations.items import Items - - sync_apis.append( - ( - Items, - "list", - TraceType.CONVERSATIONS, - self._inject_sync, - "list", - ) - ) - except ImportError: - pass - - try: - from openai.resources.conversations.items import AsyncItems - - async_apis.append( - ( - AsyncItems, - "list", - TraceType.CONVERSATIONS, - self._inject_async, - "list", - ) - ) - except ImportError: - pass - - return sync_apis, async_apis - - def _responses_api_list(self): - sync_apis, async_apis = self._responses_apis() - yield from sync_apis - yield from async_apis - - def _conversations_api_list(self): - sync_apis, async_apis = self._conversations_apis() - yield from sync_apis - yield from async_apis - - def _all_api_list(self): - yield from self._responses_api_list() - yield from self._conversations_api_list() - - def _generate_api_and_injector(self, apis): - yield from apis - - def _available_responses_apis_and_injectors(self): - """ - Generates a sequence of tuples containing Responses and Conversations API classes, method names, and - corresponding injector functions. - - :return: A generator yielding tuples. - :rtype: tuple - """ - yield from self._generate_api_and_injector(self._all_api_list()) - - def _instrument_responses(self, enable_content_tracing: bool = False, enable_binary_data: bool = False): - """This function modifies the methods of the Responses API classes to - inject logic before calling the original methods. - The original methods are stored as _original attributes of the methods. - - :param enable_content_tracing: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_tracing: bool - :param enable_binary_data: Indicates whether tracing of binary data (such as images) should be enabled. - This only takes effect when content recording is also enabled. - :type enable_binary_data: bool - """ - # pylint: disable=W0603 - global _responses_traces_enabled - global _trace_responses_content - global _trace_binary_data - if _responses_traces_enabled: - return - - _responses_traces_enabled = True - _trace_responses_content = enable_content_tracing - _trace_binary_data = enable_binary_data - - # Initialize metrics instruments - self._initialize_metrics() - - for ( - api, - method, - trace_type, - injector, - name, - ) in self._available_responses_apis_and_injectors(): - try: - setattr(api, method, injector(getattr(api, method), trace_type, name)) - except (AttributeError, ImportError) as e: - logger.debug(f"Could not instrument {api.__name__}.{method}: {e}") - - def _uninstrument_responses(self): - global _responses_traces_enabled - global _trace_responses_content - if not _responses_traces_enabled: - return - - _responses_traces_enabled = False - _trace_responses_content = False - for ( - api, - method, - trace_type, - injector, - name, - ) in self._available_responses_apis_and_injectors(): - try: - original_method = getattr(getattr(api, method), "_original", None) - if original_method: - setattr(api, method, original_method) - except (AttributeError, ImportError): - pass - - def _is_instrumented(self): - global _responses_traces_enabled - return _responses_traces_enabled - - def _set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - global _trace_responses_content - _trace_responses_content = enable_content_recording - - def _is_content_recording_enabled(self) -> bool: - global _trace_responses_content - return _trace_responses_content - - def _set_enable_binary_data(self, enable_binary_data: bool = False) -> None: - global _trace_binary_data - _trace_binary_data = enable_binary_data - - def _is_binary_data_enabled(self) -> bool: - global _trace_binary_data - return _trace_binary_data - - def record_error(self, span, exc): - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.ERROR, str(exc)) - span.span_instance.record_exception(exc) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py deleted file mode 100644 index 956b43792d71..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py +++ /dev/null @@ -1,206 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -import functools -import inspect -from typing import Any, Callable, Optional, Dict - -try: - # pylint: disable = no-name-in-module - from opentelemetry import trace as opentelemetry_trace - - _tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] - _tracing_library_available = True -except ModuleNotFoundError: - _tracing_library_available = False - -if _tracing_library_available: - - def trace_function(span_name: Optional[str] = None): - """ - A decorator for tracing function calls using OpenTelemetry. - - This decorator handles various data types for function parameters and return values, - and records them as attributes in the trace span. The supported data types include: - - Basic data types: str, int, float, bool - - Collections: list, dict, tuple, set - - Special handling for collections: - - If a collection (list, dict, tuple, set) contains nested collections, the entire collection - is converted to a string before being recorded as an attribute. - - Sets and dictionaries are always converted to strings to ensure compatibility with span attributes. - - Object types are omitted, and the corresponding parameter is not traced. - - :param span_name: The name of the span. If not provided, the function name is used. - :type span_name: Optional[str] - :return: The decorated function with tracing enabled. - :rtype: Callable - """ - - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - async def async_wrapper(*args: Any, **kwargs: Any) -> Any: - """ - Wrapper function for asynchronous functions. - - :param args: Positional arguments passed to the function. - :type args: Tuple[Any] - :return: The result of the decorated asynchronous function. - :rtype: Any - """ - tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] - name = span_name if span_name else func.__name__ - with tracer.start_as_current_span(name) as span: - try: - # Sanitize parameters and set them as attributes - sanitized_params = sanitize_parameters(func, *args, **kwargs) - span.set_attributes(sanitized_params) - result = await func(*args, **kwargs) - sanitized_result = sanitize_for_attributes(result) - if sanitized_result is not None: - if isinstance(sanitized_result, (list, dict, tuple, set)): - if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_result): - sanitized_result = str(sanitized_result) - span.set_attribute("code.function.return.value", sanitized_result) # type: ignore - return result - except Exception as e: - span.record_exception(e) - span.set_attribute("error.type", e.__class__.__qualname__) # type: ignore - raise - - @functools.wraps(func) - def sync_wrapper(*args: Any, **kwargs: Any) -> Any: - """ - Wrapper function for synchronous functions. - - :param args: Positional arguments passed to the function. - :type args: Tuple[Any] - :return: The result of the decorated synchronous function. - :rtype: Any - """ - tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] - name = span_name if span_name else func.__name__ - with tracer.start_as_current_span(name) as span: - try: - # Sanitize parameters and set them as attributes - sanitized_params = sanitize_parameters(func, *args, **kwargs) - span.set_attributes(sanitized_params) - result = func(*args, **kwargs) - sanitized_result = sanitize_for_attributes(result) - if sanitized_result is not None: - if isinstance(sanitized_result, (list, dict, tuple, set)): - if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_result): - sanitized_result = str(sanitized_result) - span.set_attribute("code.function.return.value", sanitized_result) # type: ignore - return result - except Exception as e: - span.record_exception(e) - span.set_attribute("error.type", e.__class__.__qualname__) # type: ignore - raise - - # Determine if the function is async - if inspect.iscoroutinefunction(func): - return async_wrapper - return sync_wrapper - - return decorator - -else: - # Define a no-op decorator if OpenTelemetry is not available - def trace_function(span_name: Optional[str] = None): # pylint: disable=unused-argument - """ - A no-op decorator for tracing function calls when OpenTelemetry is not available. - - :param span_name: Not used in this version. - :type span_name: Optional[str] - :return: The original function. - :rtype: Callable - """ - - def decorator(func: Callable) -> Callable: - return func - - return decorator - - -def sanitize_parameters(func, *args, **kwargs) -> Dict[str, Any]: - """ - Sanitize function parameters to include only built-in data types. - - :param func: The function being decorated. - :type func: Callable - :param args: Positional arguments passed to the function. - :type args: Tuple[Any] - :return: A dictionary of sanitized parameters. - :rtype: Dict[str, Any] - """ - params = inspect.signature(func).parameters - sanitized_params = {} - - for i, (name, param) in enumerate(params.items()): - if i < len(args): - # Use positional argument if provided - value = args[i] - else: - # Use keyword argument if provided, otherwise fall back to default value - value = kwargs.get(name, param.default) - - sanitized_value = sanitize_for_attributes(value) - # Check if the collection has nested collections - if isinstance(sanitized_value, (list, dict, tuple, set)): - if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_value): - sanitized_value = str(sanitized_value) - if sanitized_value is not None: - sanitized_params["code.function.parameter." + name] = sanitized_value - - return sanitized_params - - -# pylint: disable=R0911 -def sanitize_for_attributes(value: Any, is_recursive: bool = False) -> Any: - """ - Sanitize a value to be used as an attribute. - - :param value: The value to sanitize. - :type value: Any - :param is_recursive: Indicates if the function is being called recursively. Default is False. - :type is_recursive: bool - :return: The sanitized value or None if the value is not a supported type. - :rtype: Any - """ - if isinstance(value, (str, int, float, bool)): - return value - if isinstance(value, list): - return [ - sanitize_for_attributes(item, True) - for item in value - if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) - ] - if isinstance(value, dict): - retval = { - k: sanitize_for_attributes(v, True) - for k, v in value.items() - if isinstance(v, (str, int, float, bool, list, dict, tuple, set)) - } - # dict to compatible with span attribute, so return it as a string - if is_recursive: - return retval - return str(retval) - if isinstance(value, tuple): - return tuple( - sanitize_for_attributes(item, True) - for item in value - if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) - ) - if isinstance(value, set): - retval_set = { - sanitize_for_attributes(item, True) - for item in value - if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) - } - if is_recursive: - return retval_set - return str(retval_set) - return None diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py deleted file mode 100644 index 931c3d2abf7b..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py +++ /dev/null @@ -1,278 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -from typing import Optional -import logging -from enum import Enum - -from azure.core.tracing import AbstractSpan, SpanKind # type: ignore -from azure.core.settings import settings # type: ignore - -try: - _span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable -except ModuleNotFoundError: - _span_impl_type = None - -logger = logging.getLogger(__name__) - - -GEN_AI_MESSAGE_ID = "gen_ai.message.id" -GEN_AI_MESSAGE_STATUS = "gen_ai.message.status" -GEN_AI_THREAD_ID = "gen_ai.thread.id" -GEN_AI_THREAD_RUN_ID = "gen_ai.thread.run.id" -GEN_AI_AGENT_ID = "gen_ai.agent.id" -GEN_AI_AGENT_NAME = "gen_ai.agent.name" -GEN_AI_AGENT_DESCRIPTION = "gen_ai.agent.description" -GEN_AI_OPERATION_NAME = "gen_ai.operation.name" -GEN_AI_THREAD_RUN_STATUS = "gen_ai.thread.run.status" -GEN_AI_REQUEST_MODEL = "gen_ai.request.model" -GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature" -GEN_AI_REQUEST_TOP_P = "gen_ai.request.top_p" -GEN_AI_REQUEST_MAX_INPUT_TOKENS = "gen_ai.request.max_input_tokens" -GEN_AI_REQUEST_MAX_OUTPUT_TOKENS = "gen_ai.request.max_output_tokens" -GEN_AI_RESPONSE_MODEL = "gen_ai.response.model" -GEN_AI_SYSTEM = "gen_ai.system" -SERVER_ADDRESS = "server.address" -SERVER_PORT = "server.port" -AZ_AI_AGENT_SYSTEM = "az.ai.agents" -AZURE_AI_AGENTS = "azure.ai.agents" -AZ_NAMESPACE = "az.namespace" -AZ_NAMESPACE_VALUE = "Microsoft.CognitiveServices" -GEN_AI_TOOL_NAME = "gen_ai.tool.name" -GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id" -GEN_AI_REQUEST_RESPONSE_FORMAT = "gen_ai.request.response_format" -GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens" -GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens" -GEN_AI_SYSTEM_MESSAGE = "gen_ai.system_instructions" -GEN_AI_EVENT_CONTENT = "gen_ai.event.content" -GEN_AI_RUN_STEP_START_TIMESTAMP = "gen_ai.run_step.start.timestamp" -GEN_AI_RUN_STEP_END_TIMESTAMP = "gen_ai.run_step.end.timestamp" -GEN_AI_RUN_STEP_STATUS = "gen_ai.run_step.status" -ERROR_TYPE = "error.type" -ERROR_MESSAGE = "error.message" -GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION = "1.34.0" -GEN_AI_PROVIDER_NAME = "gen_ai.provider.name" - -# Added to the latest version, not part of semantic conventions -GEN_AI_REQUEST_REASONING_EFFORT = "gen_ai.request.reasoning.effort" -GEN_AI_REQUEST_REASONING_SUMMARY = "gen_ai.request.reasoning.summary" -GEN_AI_REQUEST_STRUCTURED_INPUTS = "gen_ai.request.structured_inputs" -GEN_AI_AGENT_VERSION = "gen_ai.agent.version" - -# Additional attribute names -GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id" -GEN_AI_CONVERSATION_ITEM_ID = "gen_ai.conversation.item.id" -GEN_AI_CONVERSATION_ITEM_ROLE = "gen_ai.conversation.item.role" -GEN_AI_REQUEST_TOOLS = "gen_ai.request.tools" -GEN_AI_RESPONSE_ID = "gen_ai.response.id" -GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT = "gen_ai.openai.response.system_fingerprint" -GEN_AI_OPENAI_RESPONSE_SERVICE_TIER = "gen_ai.openai.response.service_tier" -GEN_AI_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens" -GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons" -GEN_AI_RESPONSE_OBJECT = "gen_ai.response.object" -GEN_AI_TOKEN_TYPE = "gen_ai.token.type" -GEN_AI_MESSAGE_ROLE = "gen_ai.message.role" -GEN_AI_AGENT_TYPE = "gen_ai.agent.type" -GEN_AI_CONVERSATION_ITEM_TYPE = "gen_ai.conversation.item.type" -GEN_AI_AGENT_HOSTED_CPU = "gen_ai.agent.hosted.cpu" -GEN_AI_AGENT_HOSTED_MEMORY = "gen_ai.agent.hosted.memory" -GEN_AI_AGENT_HOSTED_IMAGE = "gen_ai.agent.hosted.image" -GEN_AI_AGENT_HOSTED_PROTOCOL = "gen_ai.agent.hosted.protocol" -GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION = "gen_ai.agent.hosted.protocol_version" - -# Event names -GEN_AI_USER_MESSAGE_EVENT = "gen_ai.input.messages" -GEN_AI_ASSISTANT_MESSAGE_EVENT = "gen_ai.output.messages" -GEN_AI_TOOL_MESSAGE_EVENT = "gen_ai.input.messages" # Keep separate constant but use same value as user messages -GEN_AI_WORKFLOW_ACTION_EVENT = "gen_ai.workflow.action" -GEN_AI_CONVERSATION_ITEM_EVENT = "gen_ai.conversation.item" -GEN_AI_SYSTEM_INSTRUCTION_EVENT = "gen_ai.system.instructions" -GEN_AI_AGENT_WORKFLOW_EVENT = "gen_ai.agent.workflow" - -# Attribute names for messages (when USE_MESSAGE_EVENTS = False) -GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages" -GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages" - -# Metric names -GEN_AI_CLIENT_OPERATION_DURATION = "gen_ai.client.operation.duration" -GEN_AI_CLIENT_TOKEN_USAGE = "gen_ai.client.token.usage" - -# Constant attribute values -AZURE_AI_AGENTS_SYSTEM = "az.ai.agents" -AGENTS_PROVIDER = "microsoft.foundry" -RESPONSES_PROVIDER = "microsoft.foundry" -AGENT_TYPE_PROMPT = "prompt" -AGENT_TYPE_WORKFLOW = "workflow" -AGENT_TYPE_HOSTED = "hosted" -AGENT_TYPE_UNKNOWN = "unknown" - -# Span name prefixes for responses API operations -SPAN_NAME_INVOKE_AGENT = "invoke_agent" -SPAN_NAME_CHAT = "chat" - -# Operation names for gen_ai.operation.name attribute -OPERATION_NAME_INVOKE_AGENT = "invoke_agent" -OPERATION_NAME_CHAT = "chat" - -# Configuration: Controls whether input/output messages are emitted as events or attributes -# Can be set at runtime for testing purposes (internal use only) -# Set to True for event-based, False for attribute-based (default) -_use_message_events = False - -# Configuration: Controls whether function tool calls use simplified OTEL-compliant format -# When True (default), function_call and function_call_output use a simpler structure: -# tool_call: {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} -# tool_call_response: {"type": "tool_call_response", "id": "...", "result": "..."} -# When False, the full nested structure is used -_use_simple_tool_format = True - - -def _get_use_simple_tool_format() -> bool: - """Get the current tool format mode (simple vs nested). Internal use only. - - :return: True if using simple OTEL-compliant format, False if using nested format - :rtype: bool - """ - return _use_simple_tool_format - - -def _set_use_simple_tool_format(use_simple: bool) -> None: - """ - Set the tool format mode at runtime. Internal use only. - - :param use_simple: True to use simple OTEL-compliant format, False for nested format - :type use_simple: bool - """ - global _use_simple_tool_format # pylint: disable=global-statement - _use_simple_tool_format = use_simple - - -def _get_use_message_events() -> bool: - """Get the current message tracing mode (events vs attributes). Internal use only. - - :return: True if using events, False if using attributes - :rtype: bool - """ - return _use_message_events - - -def _set_use_message_events(use_events: bool) -> None: - """ - Set the message tracing mode at runtime. Internal use only. - - :param use_events: True to use events (default), False to use attributes - :type use_events: bool - """ - global _use_message_events # pylint: disable=global-statement - _use_message_events = use_events - - -class OperationName(Enum): - CREATE_AGENT = "create_agent" - CREATE_THREAD = "create_thread" - CREATE_MESSAGE = "create_message" - START_THREAD_RUN = "start_thread_run" - GET_THREAD_RUN = "get_thread_run" - EXECUTE_TOOL = "execute_tool" - LIST_MESSAGES = "list_messages" - LIST_RUN_STEPS = "list_run_steps" - SUBMIT_TOOL_OUTPUTS = "submit_tool_outputs" - PROCESS_THREAD_RUN = "process_thread_run" - RESPONSES = "responses" - CREATE_CONVERSATION = "create_conversation" - LIST_CONVERSATION_ITEMS = "list_conversation_items" - - -def start_span( - operation_name: OperationName, - server_address: Optional[str], - port: Optional[int] = None, - span_name: Optional[str] = None, - thread_id: Optional[str] = None, - agent_id: Optional[str] = None, - run_id: Optional[str] = None, - model: Optional[str] = None, - temperature: Optional[float] = None, - top_p: Optional[float] = None, - max_prompt_tokens: Optional[int] = None, - max_completion_tokens: Optional[int] = None, - response_format: Optional[str] = None, - reasoning: Optional[str] = None, # pylint: disable=unused-argument - reasoning_effort: Optional[str] = None, - reasoning_summary: Optional[str] = None, - structured_inputs: Optional[str] = None, - gen_ai_system: Optional[str] = None, - gen_ai_provider: Optional[str] = AGENTS_PROVIDER, - kind: SpanKind = SpanKind.CLIENT, -) -> "Optional[AbstractSpan]": - global _span_impl_type # pylint: disable=global-statement - if _span_impl_type is None: - # Try to reinitialize the span implementation type. - # This is a workaround for the case when the tracing implementation is not set up yet when the agent telemetry is imported. - # This code should not even get called if settings.tracing_implementation() returns None since that is also checked in - # _trace_sync_function and _trace_async_function functions in the AIProjectInstrumentor. - _span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable - if _span_impl_type is None: - return None - - span = _span_impl_type( - name=span_name or operation_name.value, - kind=kind, - schema_version=GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION, - ) - - if span and span.span_instance.is_recording: - span.add_attribute(AZ_NAMESPACE, AZ_NAMESPACE_VALUE) - span.add_attribute(GEN_AI_PROVIDER_NAME, AGENTS_PROVIDER) - - if gen_ai_provider: - span.add_attribute(GEN_AI_PROVIDER_NAME, gen_ai_provider) - - if gen_ai_system: - span.add_attribute(GEN_AI_SYSTEM, gen_ai_system) - - if server_address: - span.add_attribute(SERVER_ADDRESS, server_address) - - if port is not None and port != 443: - span.add_attribute(SERVER_PORT, port) - - if thread_id: - span.add_attribute(GEN_AI_THREAD_ID, thread_id) - - if agent_id: - span.add_attribute(GEN_AI_AGENT_ID, agent_id) - - if run_id: - span.add_attribute(GEN_AI_THREAD_RUN_ID, run_id) - - if model: - span.add_attribute(GEN_AI_REQUEST_MODEL, model) - - if temperature: - span.add_attribute(GEN_AI_REQUEST_TEMPERATURE, str(temperature)) - - if top_p: - span.add_attribute(GEN_AI_REQUEST_TOP_P, str(top_p)) - - if max_prompt_tokens: - span.add_attribute(GEN_AI_REQUEST_MAX_INPUT_TOKENS, max_prompt_tokens) - - if max_completion_tokens: - span.add_attribute(GEN_AI_REQUEST_MAX_OUTPUT_TOKENS, max_completion_tokens) - - if response_format: - span.add_attribute(GEN_AI_REQUEST_RESPONSE_FORMAT, response_format) - - if reasoning_effort: - span.add_attribute(GEN_AI_REQUEST_REASONING_EFFORT, reasoning_effort) - - if reasoning_summary: - span.add_attribute(GEN_AI_REQUEST_REASONING_SUMMARY, reasoning_summary) - - if structured_inputs: - span.add_attribute(GEN_AI_REQUEST_STRUCTURED_INPUTS, structured_inputs) - - return span diff --git a/sdk/ai/azure-ai-projects/pyproject.toml b/sdk/ai/azure-ai-projects/pyproject.toml index 26eac48e1123..fe16fe054548 100644 --- a/sdk/ai/azure-ai-projects/pyproject.toml +++ b/sdk/ai/azure-ai-projects/pyproject.toml @@ -14,10 +14,10 @@ name = "azure-ai-projects" authors = [ { name = "Microsoft Corporation", email = "azpysdkhelp@microsoft.com" }, ] -description = "Microsoft Corporation Azure AI Projects Client Library for Python" +description = "Microsoft Corporation Azure Ai Projects Client Library for Python" license = "MIT" classifiers = [ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", @@ -26,7 +26,6 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", ] requires-python = ">=3.9" keywords = ["azure", "azure sdk"] @@ -44,7 +43,7 @@ dynamic = [ ] [project.urls] -repository = "https://aka.ms/azsdk/azure-ai-projects-v2/python/code" +repository = "https://github.com/Azure/azure-sdk-for-python" [tool.setuptools.dynamic] version = {attr = "azure.ai.projects._version.VERSION"} @@ -52,13 +51,13 @@ readme = {file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown"} [tool.setuptools.packages.find] exclude = [ - "azure.ai", - "azure", - "doc*", - "generated_samples*", + "tests*", "generated_tests*", "samples*", - "tests*", + "generated_samples*", + "doc*", + "azure", + "azure.ai", ] [tool.setuptools.package-data] @@ -69,4 +68,3 @@ verifytypes = false [tool.azure-sdk-conda] in_bundle = false - diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index 01ecabb49ebf..c4fa71fce5b3 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: e50321286a093e4cd6c77edfa7162d73d142f594 +commit: 57e7a9ebf3c779e4f8c90b9e140bf09c14aa0830 repo: Azure/azure-rest-api-specs additionalDirectories: From af4544218b09d8ef4934b540c931377b2c67e668 Mon Sep 17 00:00:00 2001 From: Darren Cohen <39422044+dargilco@users.noreply.github.com> Date: Thu, 26 Mar 2026 12:10:35 -0700 Subject: [PATCH 35/36] Revert "Configurations: 'specification/ai-foundry/data-plane/Foundry/tspconfig.yaml', API Version: v1, SDK Release Type: stable, and CommitSHA: '57e7a9ebf3c779e4f8c90b9e140bf09c14aa0830' in SpecRepo: 'https://github.com/Azure/azure-rest-api-specs' Pipeline run: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=6069658 Refer to https://eng.ms/docs/products/azure-developer-experience/develop/sdk-release/sdk-release-prerequisites to prepare for SDK release." This reverts commit 39fb2282e338f58e262ce21aac00fc17f1624642. --- sdk/ai/azure-ai-projects/CHANGELOG.md | 4 - sdk/ai/azure-ai-projects/_metadata.json | 6 +- .../azure/ai/projects/_patch.py | 321 +- .../azure/ai/projects/_validation.py | 66 + .../azure/ai/projects/_version.py | 2 +- .../azure/ai/projects/aio/_patch.py | 297 +- .../ai/projects/aio/operations/_operations.py | 39 +- .../ai/projects/aio/operations/_patch.py | 84 +- .../aio/operations/_patch_agents_async.py | 192 + .../operations/_patch_connections_async.py | 74 + .../aio/operations/_patch_datasets_async.py | 223 + .../_patch_evaluation_rules_async.py | 129 + .../aio/operations/_patch_evaluators_async.py | 258 + .../aio/operations/_patch_memories_async.py | 379 ++ .../aio/operations/_patch_telemetry_async.py | 75 + .../azure/ai/projects/models/_enums.py | 8 +- .../azure/ai/projects/models/_models.py | 18 +- .../azure/ai/projects/models/_patch.py | 352 +- .../ai/projects/operations/_operations.py | 39 +- .../azure/ai/projects/operations/_patch.py | 139 +- .../ai/projects/operations/_patch_agents.py | 218 + .../projects/operations/_patch_connections.py | 63 + .../ai/projects/operations/_patch_datasets.py | 222 + .../operations/_patch_evaluation_rules.py | 130 + .../projects/operations/_patch_evaluators.py | 258 + .../ai/projects/operations/_patch_memories.py | 414 ++ .../projects/operations/_patch_telemetry.py | 69 + .../azure/ai/projects/telemetry/__init__.py | 12 + .../telemetry/_ai_project_instrumentor.py | 1535 ++++++ .../telemetry/_responses_instrumentor.py | 4883 +++++++++++++++++ .../ai/projects/telemetry/_trace_function.py | 206 + .../azure/ai/projects/telemetry/_utils.py | 278 + sdk/ai/azure-ai-projects/pyproject.toml | 18 +- sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 34 files changed, 10933 insertions(+), 80 deletions(-) create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py create mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index a6bbf0926b62..b254a155fd3d 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -1,9 +1,5 @@ # Release History -## 2.1.0 (2026-03-26) - -skip changelog generation for data-plane package and please add changelog manually. - ## 2.0.2 (Unreleased) ### Features Added diff --git a/sdk/ai/azure-ai-projects/_metadata.json b/sdk/ai/azure-ai-projects/_metadata.json index 74dccbab5ffe..3a000fe50d57 100644 --- a/sdk/ai/azure-ai-projects/_metadata.json +++ b/sdk/ai/azure-ai-projects/_metadata.json @@ -2,9 +2,5 @@ "apiVersion": "v1", "apiVersions": { "Azure.AI.Projects": "v1" - }, - "commit": "57e7a9ebf3c779e4f8c90b9e140bf09c14aa0830", - "repository_url": "https://github.com/Azure/azure-rest-api-specs", - "typespec_src": "specification/ai-foundry/data-plane/Foundry", - "emitterVersion": "0.61.1" + } } \ No newline at end of file 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 87676c65a8f0..0b3aca9db700 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -1,15 +1,324 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------- +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +import os +import re +import logging +from typing import List, Any, Union +import httpx # pylint: disable=networking-import-outside-azure-core-transport +from openai import OpenAI +from azure.core.credentials import AzureKeyCredential +from azure.core.tracing.decorator import distributed_trace +from azure.core.credentials import TokenCredential +from azure.identity import get_bearer_token_provider +from ._client import AIProjectClient as AIProjectClientGenerated +from .operations import TelemetryOperations -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level +logger = logging.getLogger(__name__) + + +class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-instance-attributes + """AIProjectClient. + + :ivar beta: BetaOperations operations + :vartype beta: azure.ai.projects.operations.BetaOperations + :ivar agents: AgentsOperations operations + :vartype agents: azure.ai.projects.operations.AgentsOperations + :ivar evaluation_rules: EvaluationRulesOperations operations + :vartype evaluation_rules: azure.ai.projects.operations.EvaluationRulesOperations + :ivar connections: ConnectionsOperations operations + :vartype connections: azure.ai.projects.operations.ConnectionsOperations + :ivar datasets: DatasetsOperations operations + :vartype datasets: azure.ai.projects.operations.DatasetsOperations + :ivar deployments: DeploymentsOperations operations + :vartype deployments: azure.ai.projects.operations.DeploymentsOperations + :ivar indexes: IndexesOperations operations + :vartype indexes: azure.ai.projects.operations.IndexesOperations + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials.TokenCredential + :param allow_preview: Whether to enable preview features. Optional, default is False. + Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) + or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). + Set this to True to use human evaluation rule action (class :class:`~azure.ai.projects.models.HumanEvaluationPreviewRuleAction`). + Methods on the `.beta` sub-client (class :class:`~azure.ai.projects.operations.BetaOperations`) + are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. + When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` + with the appropriate value in all relevant calls to the service. + :type allow_preview: bool + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. + :paramtype api_version: str + """ + + def __init__( + self, + endpoint: str, + credential: Union[AzureKeyCredential, "TokenCredential"], + *, + allow_preview: bool = False, + **kwargs: Any, + ) -> None: + + self._console_logging_enabled: bool = ( + os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" + ) + + if self._console_logging_enabled: + import sys + + # Enable detailed console logs across Azure libraries + azure_logger = logging.getLogger("azure") + azure_logger.setLevel(logging.DEBUG) + console_handler = logging.StreamHandler(stream=sys.stdout) + console_handler.addFilter(_AuthSecretsFilter()) + azure_logger.addHandler(console_handler) + # Exclude detailed logs for network calls associated with getting Entra ID token. + logging.getLogger("azure.identity").setLevel(logging.ERROR) + # Make sure regular (redacted) detailed azure.core logs are not shown, as we are about to + # turn on non-redacted logs by passing 'logging_enable=True' to the client constructor + # (which are implemented as a separate logging policy) + logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.ERROR) + + kwargs.setdefault("logging_enable", self._console_logging_enabled) + + self._kwargs = kwargs.copy() + self._custom_user_agent = self._kwargs.get("user_agent", None) + + super().__init__(endpoint=endpoint, credential=credential, allow_preview=allow_preview, **kwargs) + + self.telemetry = TelemetryOperations(self) # type: ignore + + @distributed_trace + def get_openai_client(self, **kwargs: Any) -> OpenAI: + """Get an authenticated OpenAI client from the `openai` package. + + Keyword arguments are passed to the OpenAI client constructor. + + The OpenAI client constructor is called with: + * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. + Can be overridden by passing ``base_url`` as a keyword argument. + * If :class:`~azure.ai.projects.AIProjectClient` was constructed with a bearer token, ``api_key`` is set + to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient + constructor, with scope ``https://ai.azure.com/.default``. + Can be overridden by passing ``api_key`` as a keyword argument. + * If :class:`~azure.ai.projects.AIProjectClient` was constructed with ``api-key``, it is passed to the + OpenAI constructor as is. + Can be overridden by passing ``api_key`` as a keyword argument. + + .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. + + :return: An authenticated OpenAI client + :rtype: ~openai.OpenAI + + :raises ~azure.core.exceptions.HttpResponseError: + """ + + kwargs = kwargs.copy() if kwargs else {} + + # Allow caller to override base_url + if "base_url" in kwargs: + base_url = kwargs.pop("base_url") + else: + base_url = self._config.endpoint.rstrip("/") + "/openai/v1" # pylint: disable=protected-access + + 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, + ) + + # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor + if "api_key" in kwargs: + api_key = kwargs.pop("api_key") + else: + api_key = ( + self._config.credential.key # pylint: disable=protected-access + if isinstance(self._config.credential, AzureKeyCredential) + else get_bearer_token_provider( + self._config.credential, # pylint: disable=protected-access + "https://ai.azure.com/.default", + ) + ) + + if "http_client" in kwargs: + http_client = kwargs.pop("http_client") + elif self._console_logging_enabled: + http_client = httpx.Client(transport=OpenAILoggingTransport()) + else: + http_client = None + + default_headers = dict[str, str](kwargs.pop("default_headers", None) or {}) + + openai_custom_user_agent = default_headers.get("User-Agent", None) + + def _create_openai_client(**kwargs) -> OpenAI: + return OpenAI( + api_key=api_key, + base_url=base_url, + http_client=http_client, + **kwargs, + ) + + dummy_client = _create_openai_client() + + openai_default_user_agent = dummy_client.user_agent + + if openai_custom_user_agent: + final_user_agent = openai_custom_user_agent + else: + final_user_agent = ( + "-".join(ua for ua in [self._custom_user_agent, "AIProjectClient"] if ua) + + " " + + openai_default_user_agent + ) + + default_headers["User-Agent"] = final_user_agent + + client = _create_openai_client(default_headers=default_headers, **kwargs) + + return client + + +class _AuthSecretsFilter(logging.Filter): + """Redact bearer tokens and api-key values in azure.core log messages before they are emitted to console.""" + + _AUTH_HEADER_DICT_PATTERN = re.compile( + r"(?i)(['\"]authorization['\"]\ *:\ *['\"])bearer\s+[^'\"]+(['\"])", + ) + + _API_KEY_HEADER_DICT_PATTERN = re.compile( + r"(?i)(['\"]api-key['\"]\ *:\ *['\"])[^'\"]+(['\"])", + ) + + def filter(self, record: logging.LogRecord) -> bool: + rendered = record.getMessage() + redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) + redacted = self._API_KEY_HEADER_DICT_PATTERN.sub(r"\1\2", redacted) + if redacted != rendered: + # Replace the pre-formatted content so handlers emit sanitized output. + record.msg = redacted + record.args = () + return True + + +class OpenAILoggingTransport(httpx.HTTPTransport): + """Custom HTTP transport that logs OpenAI API requests and responses to the console. + + This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and print + detailed request/response information for debugging purposes. It automatically + redacts sensitive authorization headers and handles various content types including + multipart form data (file uploads). + + Used internally by AIProjectClient when console logging is enabled via the + AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable. + """ + + def _sanitize_auth_header(self, headers) -> None: + """Sanitize authorization and api-key headers by redacting sensitive information. + + :param headers: Dictionary of HTTP headers to sanitize + :type headers: dict + """ + + if "authorization" in headers: + auth_value = headers["authorization"] + if len(auth_value) >= 7: + headers["authorization"] = auth_value[:7] + "" + else: + headers["authorization"] = "" + + def handle_request(self, request: httpx.Request) -> httpx.Response: + """ + Log HTTP request and response details to console, in a nicely formatted way, + for OpenAI / Azure OpenAI clients. + + :param request: The HTTP request to handle and log + :type request: httpx.Request + + :return: The HTTP response received + :rtype: httpx.Response + """ + + print(f"\n==> Request:\n{request.method} {request.url}") + headers = dict(request.headers) + self._sanitize_auth_header(headers) + print("Headers:") + for key, value in sorted(headers.items()): + print(f" {key}: {value}") + + self._log_request_body(request) + + response = super().handle_request(request) + + print(f"\n<== Response:\n{response.status_code} {response.reason_phrase}") + print("Headers:") + for key, value in sorted(dict(response.headers).items()): + print(f" {key}: {value}") + + content = response.read() + if content is None or content == b"": + print("Body: [No content]") + else: + try: + print(f"Body:\n {content.decode('utf-8')}") + except Exception: # pylint: disable=broad-exception-caught + print(f"Body (raw):\n {content!r}") + print("\n") + + return response + + def _log_request_body(self, request: httpx.Request) -> None: + """Log request body content safely, handling binary data and streaming content. + + :param request: The HTTP request object containing the body to log + :type request: httpx.Request + """ + + # Check content-type header to identify file uploads + content_type = request.headers.get("content-type", "").lower() + if "multipart/form-data" in content_type: + print("Body: [Multipart form data - file upload, not logged]") + return + + # Safely check if content exists without accessing it + if not hasattr(request, "content"): + print("Body: [No content attribute]") + return + + # Very careful content access - wrap in try-catch immediately + try: + content = request.content + except Exception as access_error: # pylint: disable=broad-exception-caught + print(f"Body: [Cannot access content: {access_error}]") + return + + if content is None or content == b"": + print("Body: [No content]") + return + + try: + print(f"Body:\n {content.decode('utf-8')}") + except Exception: # pylint: disable=broad-exception-caught + print(f"Body (raw):\n {content!r}") + + +__all__: List[str] = [ + "AIProjectClient", +] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py new file mode 100644 index 000000000000..f5af3a4eb8a2 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py @@ -0,0 +1,66 @@ +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +import functools + + +def api_version_validation(**kwargs): + params_added_on = kwargs.pop("params_added_on", {}) + method_added_on = kwargs.pop("method_added_on", "") + api_versions_list = kwargs.pop("api_versions_list", []) + + def _index_with_default(value: str, default: int = -1) -> int: + """Get the index of value in lst, or return default if not found. + + :param value: The value to search for in the api_versions_list. + :type value: str + :param default: The default value to return if the value is not found. + :type default: int + :return: The index of the value in the list, or the default value if not found. + :rtype: int + """ + try: + return api_versions_list.index(value) + except ValueError: + return default + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + # this assumes the client has an _api_version attribute + client = args[0] + client_api_version = client._config.api_version # pylint: disable=protected-access + except AttributeError: + return func(*args, **kwargs) + + if _index_with_default(method_added_on) > _index_with_default(client_api_version): + raise ValueError( + f"'{func.__name__}' is not available in API version " + f"{client_api_version}. Pass service API version {method_added_on} or newer to your client." + ) + + unsupported = { + parameter: api_version + for api_version, parameters in params_added_on.items() + for parameter in parameters + if parameter in kwargs and _index_with_default(api_version) > _index_with_default(client_api_version) + } + if unsupported: + raise ValueError( + "".join( + [ + f"'{param}' is not available in API version {client_api_version}. " + f"Use service API version {version} or newer.\n" + for param, version in unsupported.items() + ] + ) + ) + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py index ccb75164d3bc..85d7b3924370 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "2.1.0" +VERSION = "2.0.2" 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 87676c65a8f0..b79f5c6a93e8 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 @@ -1,15 +1,300 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------- +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +import os +import logging +from typing import List, Any, Union +import httpx # pylint: disable=networking-import-outside-azure-core-transport +from openai import AsyncOpenAI +from azure.core.credentials import AzureKeyCredential +from azure.core.tracing.decorator import distributed_trace +from azure.core.credentials_async import AsyncTokenCredential +from azure.identity.aio import get_bearer_token_provider +from .._patch import _AuthSecretsFilter +from ._client import AIProjectClient as AIProjectClientGenerated +from .operations import TelemetryOperations -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level +logger = logging.getLogger(__name__) + + +class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-instance-attributes + """AIProjectClient. + + :ivar beta: BetaOperations operations + :vartype beta: azure.ai.projects.aio.operations.BetaOperations + :ivar agents: AgentsOperations operations + :vartype agents: azure.ai.projects.aio.operations.AgentsOperations + :ivar evaluation_rules: EvaluationRulesOperations operations + :vartype evaluation_rules: azure.ai.projects.aio.operations.EvaluationRulesOperations + :ivar connections: ConnectionsOperations operations + :vartype connections: azure.ai.projects.aio.operations.ConnectionsOperations + :ivar datasets: DatasetsOperations operations + :vartype datasets: azure.ai.projects.aio.operations.DatasetsOperations + :ivar deployments: DeploymentsOperations operations + :vartype deployments: azure.ai.projects.aio.operations.DeploymentsOperations + :ivar indexes: IndexesOperations operations + :vartype indexes: azure.ai.projects.aio.operations.IndexesOperations + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Is either a key + credential type or a token credential type. Required. + :type credential: ~azure.core.credentials.AzureKeyCredential or + ~azure.core.credentials_async.AsyncTokenCredential + :param allow_preview: Whether to enable preview features. Optional, default is False. + Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) + or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). + Set this to True to use human evaluation rule action (class :class:`~azure.ai.projects.models.HumanEvaluationPreviewRuleAction`). + Methods on the `.beta` sub-client (class :class:`~azure.ai.projects.aio.operations.BetaOperations`) + are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. + When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` + with the appropriate value in all relevant calls to the service. + :type allow_preview: bool + :keyword api_version: The API version to use for this operation. Known values are "v1". Default + value is "v1". Note that overriding this default value may result in unsupported behavior. + :paramtype api_version: str + """ + + def __init__( + self, + endpoint: str, + credential: Union[AzureKeyCredential, "AsyncTokenCredential"], + *, + allow_preview: bool = False, + **kwargs: Any, + ) -> None: + + self._console_logging_enabled: bool = ( + os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" + ) + + if self._console_logging_enabled: + import sys + + # Enable detailed console logs across Azure libraries + azure_logger = logging.getLogger("azure") + azure_logger.setLevel(logging.DEBUG) + console_handler = logging.StreamHandler(stream=sys.stdout) + console_handler.addFilter(_AuthSecretsFilter()) + azure_logger.addHandler(console_handler) + # Exclude detailed logs for network calls associated with getting Entra ID token. + logging.getLogger("azure.identity").setLevel(logging.ERROR) + # Make sure regular (redacted) detailed azure.core logs are not shown, as we are about to + # turn on non-redacted logs by passing 'logging_enable=True' to the client constructor + # (which are implemented as a separate logging policy) + logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.ERROR) + + kwargs.setdefault("logging_enable", self._console_logging_enabled) + + self._kwargs = kwargs.copy() + self._custom_user_agent = self._kwargs.get("user_agent", None) + + super().__init__(endpoint=endpoint, credential=credential, allow_preview=allow_preview, **kwargs) + + self.telemetry = TelemetryOperations(self) # type: ignore + + @distributed_trace + def get_openai_client(self, **kwargs: Any) -> AsyncOpenAI: + """Get an authenticated AsyncOpenAI client from the `openai` package. + + Keyword arguments are passed to the AsyncOpenAI client constructor. + + The AsyncOpenAI client constructor is called with: + * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. + Can be overridden by passing ``base_url`` as a keyword argument. + * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with a bearer token, ``api_key`` is set + to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient + constructor, with scope ``https://ai.azure.com/.default``. + Can be overridden by passing ``api_key`` as a keyword argument. + * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with ``api-key``, it is passed to the + OpenAI constructor as is. + Can be overridden by passing ``api_key`` as a keyword argument. + + .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. + + :return: An authenticated AsyncOpenAI client + :rtype: ~openai.AsyncOpenAI + + :raises ~azure.core.exceptions.HttpResponseError: + """ + + kwargs = kwargs.copy() if kwargs else {} + + # Allow caller to override base_url + if "base_url" in kwargs: + base_url = kwargs.pop("base_url") + else: + base_url = self._config.endpoint.rstrip("/") + "/openai/v1" # pylint: disable=protected-access + + 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, + ) + + # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor + if "api_key" in kwargs: + api_key = kwargs.pop("api_key") + else: + api_key = ( + self._config.credential.key # pylint: disable=protected-access + if isinstance(self._config.credential, AzureKeyCredential) + else get_bearer_token_provider( + self._config.credential, # pylint: disable=protected-access + "https://ai.azure.com/.default", + ) + ) + + if "http_client" in kwargs: + http_client = kwargs.pop("http_client") + elif self._console_logging_enabled: + http_client = httpx.AsyncClient(transport=OpenAILoggingTransport()) + else: + http_client = None + + default_headers = dict[str, str](kwargs.pop("default_headers", None) or {}) + + openai_custom_user_agent = default_headers.get("User-Agent", None) + + def _create_openai_client(**kwargs) -> AsyncOpenAI: + return AsyncOpenAI( + api_key=api_key, + base_url=base_url, + http_client=http_client, + **kwargs, + ) + + dummy_client = _create_openai_client() + + openai_default_user_agent = dummy_client.user_agent + + if openai_custom_user_agent: + final_user_agent = openai_custom_user_agent + else: + final_user_agent = ( + "-".join(ua for ua in [self._custom_user_agent, "AIProjectClient"] if ua) + + " " + + openai_default_user_agent + ) + + default_headers["User-Agent"] = final_user_agent + + client = _create_openai_client(default_headers=default_headers, **kwargs) + + return client + + +class OpenAILoggingTransport(httpx.AsyncHTTPTransport): + """Custom HTTP async transport that logs OpenAI API requests and responses to the console. + + This transport wraps httpx.AsyncHTTPTransport to intercept all HTTP traffic and print + detailed request/response information for debugging purposes. It automatically + redacts sensitive authorization headers and handles various content types including + multipart form data (file uploads). + + Used internally by AIProjectClient when console logging is enabled via the + AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable. + """ + + def _sanitize_auth_header(self, headers): + """Sanitize authorization and api-key headers by redacting sensitive information. + + :param headers: Dictionary of HTTP headers to sanitize + :type headers: dict + """ + + if "authorization" in headers: + auth_value = headers["authorization"] + if len(auth_value) >= 7: + headers["authorization"] = auth_value[:7] + "" + else: + headers["authorization"] = "" + + async def handle_async_request(self, request: httpx.Request) -> httpx.Response: + """ + Log HTTP request and response details to console, in a nicely formatted way, + for OpenAI / Azure OpenAI clients. + + :param request: The HTTP request to handle and log + :type request: httpx.Request + + :return: The HTTP response received + :rtype: httpx.Response + """ + + print(f"\n==> Request:\n{request.method} {request.url}") + headers = dict(request.headers) + self._sanitize_auth_header(headers) + print("Headers:") + for key, value in sorted(headers.items()): + print(f" {key}: {value}") + + self._log_request_body(request) + + response = await super().handle_async_request(request) + + print(f"\n<== Response:\n{response.status_code} {response.reason_phrase}") + print("Headers:") + for key, value in sorted(dict(response.headers).items()): + print(f" {key}: {value}") + + content = await response.aread() + if content is None or content == b"": + print("Body: [No content]") + else: + try: + print(f"Body:\n {content.decode('utf-8')}") + except Exception: # pylint: disable=broad-exception-caught + print(f"Body (raw):\n {content!r}") + print("\n") + + return response + + def _log_request_body(self, request: httpx.Request) -> None: + """Log request body content safely, handling binary data and streaming content. + + :param request: The HTTP request object containing the body to log + :type request: httpx.Request + """ + + # Check content-type header to identify file uploads + content_type = request.headers.get("content-type", "").lower() + if "multipart/form-data" in content_type: + print("Body: [Multipart form data - file upload, not logged]") + return + + # Safely check if content exists without accessing it + if not hasattr(request, "content"): + print("Body: [No content attribute]") + return + + # Very careful content access - wrap in try-catch immediately + try: + content = request.content + except Exception as access_error: # pylint: disable=broad-exception-caught + print(f"Body: [Cannot access content: {access_error}]") + return + + if content is None or content == b"": + print("Body: [No content]") + return + + try: + print(f"Body:\n {content.decode('utf-8')}") + except Exception: # pylint: disable=broad-exception-caught + print(f"Body (raw):\n {content!r}") + + +__all__: List[str] = ["AIProjectClient"] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 7bffabfd89e1..50659948669c 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -3097,7 +3097,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3527,7 +3530,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3627,7 +3633,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -4684,7 +4693,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5352,7 +5364,7 @@ async def _search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -5438,7 +5450,7 @@ async def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, @@ -5886,7 +5898,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6232,7 +6247,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6532,7 +6550,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index 87676c65a8f0..c448e46abae8 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -1,15 +1,87 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------- +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +from typing import Any, List +from ._patch_agents_async import AgentsOperations +from ._patch_datasets_async import DatasetsOperations +from ._patch_evaluation_rules_async import EvaluationRulesOperations +from ._patch_evaluators_async import BetaEvaluatorsOperations +from ._patch_telemetry_async import TelemetryOperations +from ._patch_connections_async import ConnectionsOperations +from ._patch_memories_async import BetaMemoryStoresOperations +from ...operations._patch import _BETA_OPERATION_FEATURE_HEADERS, _OperationMethodHeaderProxy +from ._operations import ( + BetaEvaluationTaxonomiesOperations, + BetaInsightsOperations, + BetaOperations as GeneratedBetaOperations, + BetaRedTeamsOperations, + BetaSchedulesOperations, + BetaToolsetsOperations, +) -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level + +class BetaOperations(GeneratedBetaOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`beta` attribute. + """ + + evaluation_taxonomies: BetaEvaluationTaxonomiesOperations + """:class:`~azure.ai.projects.aio.operations.BetaEvaluationTaxonomiesOperations` operations""" + evaluators: BetaEvaluatorsOperations + """:class:`~azure.ai.projects.aio.operations.BetaEvaluatorsOperations` operations""" + insights: BetaInsightsOperations + """:class:`~azure.ai.projects.aio.operations.BetaInsightsOperations` operations""" + memory_stores: BetaMemoryStoresOperations + """:class:`~azure.ai.projects.aio.operations.BetaMemoryStoresOperations` operations""" + red_teams: BetaRedTeamsOperations + """:class:`~azure.ai.projects.aio.operations.BetaRedTeamsOperations` operations""" + schedules: BetaSchedulesOperations + """:class:`~azure.ai.projects.aio.operations.BetaSchedulesOperations` operations""" + toolsets: BetaToolsetsOperations + """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + # Replace with patched class that includes upload() + self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) + # Replace with patched class that includes begin_update_memories + self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) + + for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): + setattr( + self, + property_name, + _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), + ) + + +__all__: List[str] = [ + "AgentsOperations", + "BetaEvaluationTaxonomiesOperations", + "BetaEvaluatorsOperations", + "BetaInsightsOperations", + "BetaMemoryStoresOperations", + "BetaOperations", + "BetaRedTeamsOperations", + "BetaSchedulesOperations", + "BetaToolsetsOperations", + "ConnectionsOperations", + "DatasetsOperations", + "EvaluationRulesOperations", + "TelemetryOperations", +] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py new file mode 100644 index 000000000000..f2a6dbc62624 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py @@ -0,0 +1,192 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Optional, Any, IO, overload +from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator_async import distributed_trace_async +from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset +from ... import models as _models +from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive +from ...operations._patch_agents import ( + _AGENT_OPERATION_FEATURE_HEADERS, + _PREVIEW_FEATURE_REQUIRED_CODE, + _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE, +) + + +class AgentsOperations(GeneratedAgentsOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`agents` attribute. + """ + + @overload + async def create_version( + self, + agent_name: str, + *, + definition: _models.AgentDefinition, + content_type: str = "application/json", + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_version( + self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_version( + self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @distributed_trace_async + async def create_version( + self, + agent_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + definition: _models.AgentDefinition = _Unset, + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS + kwargs["headers"] = headers + + try: + return await super().create_version( + agent_name, + body, + definition=definition, + metadata=metadata, + description=description, + **kwargs, + ) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py new file mode 100644 index 000000000000..f32515aca2fe --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py @@ -0,0 +1,74 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Any, Optional, Union +from azure.core.tracing.decorator_async import distributed_trace_async + +from ._operations import ConnectionsOperations as ConnectionsOperationsGenerated +from ...models._models import Connection +from ...models._enums import ConnectionType + + +class ConnectionsOperations(ConnectionsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`connections` attribute. + """ + + @distributed_trace_async + async def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: + """Get a connection by name. + + :param name: The name of the resource. Required. + :type name: str + :keyword include_credentials: Whether to include credentials in the response. Default is False. + :paramtype include_credentials: bool + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ~azure.core.exceptions.HttpResponseError: + """ + + if include_credentials: + connection = await super()._get_with_credentials(name, **kwargs) + if connection.type == ConnectionType.CUSTOM: + # Why do we do this? See comment in the sync version of this code (file _patch_connections.py). + setattr( + connection.credentials, + "credential_keys", + {k: v for k, v in connection.credentials.as_dict().items() if k != "type"}, + ) + + return connection + + return await super()._get(name, **kwargs) + + @distributed_trace_async + async def get_default( + self, connection_type: Union[str, ConnectionType], *, include_credentials: Optional[bool] = False, **kwargs: Any + ) -> Connection: + """Get the default connection for a given connection type. + + :param connection_type: The type of the connection. Required. + :type connection_type: str or ~azure.ai.projects.models.ConnectionType + :keyword include_credentials: Whether to include credentials in the response. Default is False. + :paramtype include_credentials: bool + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ValueError: If no default connection is found for the given type. + :raises ~azure.core.exceptions.HttpResponseError: + """ + connections = super().list(connection_type=connection_type, default_connection=True, **kwargs) + async for connection in connections: + return await self.get(connection.name, include_credentials=include_credentials, **kwargs) + raise ValueError(f"No default connection found for type: {connection_type}.") diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py new file mode 100644 index 000000000000..dc7095c827ea --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py @@ -0,0 +1,223 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +import os +import re +import logging +from typing import Any, Tuple, Optional +from pathlib import Path +from urllib.parse import urlsplit +from azure.storage.blob.aio import ContainerClient +from azure.core.tracing.decorator_async import distributed_trace_async + +from ._operations import DatasetsOperations as DatasetsOperationsGenerated +from ...models._models import ( + FileDatasetVersion, + FolderDatasetVersion, + PendingUploadRequest, + PendingUploadResponse, + PendingUploadType, +) + +logger = logging.getLogger(__name__) + + +class DatasetsOperations(DatasetsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`datasets` attribute. + """ + + # Internal helper method to create a new dataset and return a ContainerClient from azure-storage-blob package, + # to the dataset's blob storage. + async def _create_dataset_and_get_its_container_client( + self, + name: str, + input_version: str, + connection_name: Optional[str] = None, + ) -> Tuple[ContainerClient, str]: + + pending_upload_response: PendingUploadResponse = await self.pending_upload( + name=name, + version=input_version, + pending_upload_request=PendingUploadRequest( + pending_upload_type=PendingUploadType.BLOB_REFERENCE, + connection_name=connection_name, + ), + ) + output_version: str = input_version + + if not pending_upload_response.blob_reference: + raise ValueError("Blob reference is not present") + if not pending_upload_response.blob_reference.credential: + raise ValueError("SAS credential are not present") + if not pending_upload_response.blob_reference.credential.sas_uri: + raise ValueError("SAS URI is missing or empty") + + # For overview on Blob storage SDK in Python see: + # https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python + # https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-python + + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-from-container-url + return ( + ContainerClient.from_container_url( + container_url=pending_upload_response.blob_reference.credential.sas_uri, # Of the form: "https://.blob.core.windows.net/?" + ), + output_version, + ) + + @distributed_trace_async + async def upload_file( + self, *, name: str, version: str, file_path: str, connection_name: Optional[str] = None, **kwargs: Any + ) -> FileDatasetVersion: + """Upload file to a blob storage, and create a dataset that references this file. + This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package + to upload the file. Any keyword arguments provided will be passed to the `upload_blob` method. + + :keyword name: The name of the dataset. Required. + :paramtype name: str + :keyword version: The version identifier for the dataset. Required. + :paramtype version: str + :keyword file_path: The file name (including optional path) to be uploaded. Required. + :paramtype file_path: str + :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. + If not specified, the default Azure Storage Account connection will be used. Optional. + :paramtype connection_name: str + :return: The created dataset version. + :rtype: ~azure.ai.projects.models.FileDatasetVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + + pathlib_file_path = Path(file_path) + if not pathlib_file_path.exists(): + raise ValueError(f"The provided file `{file_path}` does not exist.") + if pathlib_file_path.is_dir(): + raise ValueError("The provided file is actually a folder. Use method `upload_folder` instead") + + container_client, output_version = await self._create_dataset_and_get_its_container_client( + name=name, + input_version=version, + connection_name=connection_name, + ) + + async with container_client: + + with open(file=file_path, mode="rb") as data: # TODO: What is the best async options for file reading? + + blob_name = pathlib_file_path.name # Extract the file name from the path. + logger.debug( + "[upload_file] Start uploading file `%s` as blob `%s`.", + file_path, + blob_name, + ) + + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-upload-blob + async with await container_client.upload_blob(name=blob_name, data=data, **kwargs) as blob_client: + logger.debug("[upload_file] Done uploading") + + # Remove the SAS token from the URL (remove all query strings). + # The resulting format should be "https://.blob.core.windows.net//" + data_uri = urlsplit(blob_client.url)._replace(query="").geturl() + + dataset_version = await self.create_or_update( + name=name, + version=output_version, + dataset_version=FileDatasetVersion( + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#azure-storage-blob-blobclient-url + # Per above doc the ".url" contains SAS token... should this be stripped away? + data_uri=data_uri, + ), + ) + + return dataset_version # type: ignore + + @distributed_trace_async + async def upload_folder( + self, + *, + name: str, + version: str, + folder: str, + connection_name: Optional[str] = None, + file_pattern: Optional[re.Pattern] = None, + **kwargs: Any, + ) -> FolderDatasetVersion: + """Upload all files in a folder and its sub folders to a blob storage, while maintaining + relative paths, and create a dataset that references this folder. + This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package + to upload each file. Any keyword arguments provided will be passed to the `upload_blob` method. + + :keyword name: The name of the dataset. Required. + :paramtype name: str + :keyword version: The version identifier for the dataset. Required. + :paramtype version: str + :keyword folder: The folder name (including optional path) to be uploaded. Required. + :paramtype folder: str + :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. + If not specified, the default Azure Storage Account connection will be used. Optional. + :paramtype connection_name: str + :keyword file_pattern: A regex pattern to filter files to be uploaded. Only files matching the pattern + will be uploaded. Optional. + :paramtype file_pattern: re.Pattern + :return: The created dataset version. + :rtype: ~azure.ai.projects.models.FolderDatasetVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + path_folder = Path(folder) + if not Path(path_folder).exists(): + raise ValueError(f"The provided folder `{folder}` does not exist.") + if Path(path_folder).is_file(): + raise ValueError("The provided folder is actually a file. Use method `upload_file` instead.") + + container_client, output_version = await self._create_dataset_and_get_its_container_client( + name=name, input_version=version, connection_name=connection_name + ) + + async with container_client: + + # Recursively traverse all files in the folder + files_uploaded: bool = False + for root, _, files in os.walk(folder): + for file in files: + if file_pattern and not file_pattern.search(file): + continue # Skip files that do not match the pattern + file_path = os.path.join(root, file) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") # Ensure correct format for Azure + logger.debug( + "[upload_folder] Start uploading file `%s` as blob `%s`.", + file_path, + blob_name, + ) + with open(file=file_path, mode="rb") as data: # Open the file for reading in binary mode + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-upload-blob + await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + logger.debug("[upload_folder] Done uploading file") + files_uploaded = True + logger.debug("[upload_folder] Done uploaded.") + + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + # Remove the SAS token from the URL (remove all query strings). + # The resulting format should be "https://.blob.core.windows.net/" + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-url + data_uri = urlsplit(container_client.url)._replace(query="").geturl() + + dataset_version = await self.create_or_update( + name=name, + version=output_version, + dataset_version=FolderDatasetVersion(data_uri=data_uri), + ) + + return dataset_version # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py new file mode 100644 index 000000000000..a296493c469b --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py @@ -0,0 +1,129 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Any, IO, overload +from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator_async import distributed_trace_async +from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON +from ... import models as _models +from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE +from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive + + +class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`evaluation_rules` attribute. + """ + + @overload + async def create_or_update( + self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_or_update( + self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + async def create_or_update( + self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @distributed_trace_async + async def create_or_update( + self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Is one of the following types: + EvaluationRule, JSON, IO[bytes] Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = { + _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + } + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + kwargs["headers"] = headers + + try: + return await super().create_or_update(id, evaluation_rule, **kwargs) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py new file mode 100644 index 000000000000..c6c366fd5956 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py @@ -0,0 +1,258 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +import os +import logging +from typing import Any, Final, IO, Tuple, Optional, Union +from pathlib import Path +from urllib.parse import urlsplit +from azure.storage.blob.aio import ContainerClient +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError +from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON +from ...models._enums import _FoundryFeaturesOptInKeys +from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME +from ...models._models import ( + CodeBasedEvaluatorDefinition, + EvaluatorVersion, +) + +logger = logging.getLogger(__name__) + +_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + + +class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`beta.evaluators` attribute. + """ + + @staticmethod + async def _upload_folder_to_blob( + container_client: ContainerClient, + folder: str, + **kwargs: Any, + ) -> None: + """Walk *folder* and upload every eligible file to the blob container. + + Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` + directories and ``.pyc`` / ``.pyo`` files. + + :param container_client: The blob container client to upload files to. + :type container_client: ~azure.storage.blob.ContainerClient + :param folder: Path to the local folder containing files to upload. + :type folder: str + :raises ValueError: If the folder contains no uploadable files. + :raises HttpResponseError: Re-raised with a friendlier message on + ``AuthorizationPermissionMismatch``. + """ + skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} + skip_extensions = {".pyc", ".pyo"} + files_uploaded = False + + for root, dirs, files in os.walk(folder): + dirs[:] = [d for d in dirs if d not in skip_dirs] + for file_name in files: + if any(file_name.endswith(ext) for ext in skip_extensions): + continue + file_path = os.path.join(root, file_name) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") + logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) + with open(file=file_path, mode="rb") as data: + try: + await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + except HttpResponseError as e: + if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": + storage_account = urlsplit(container_client.url).hostname + raise HttpResponseError( + message=( + f"Failed to upload file '{blob_name}' to blob storage: " + f"permission denied. Ensure the identity that signed the SAS token " + f"has the 'Storage Blob Data Contributor' role on the storage account " + f"'{storage_account}'. " + f"Original error: {e.message}" + ), + response=e.response, + ) from e + raise + logger.debug("[upload] Done uploading file") + files_uploaded = True + + logger.debug("[upload] Done uploading all files.") + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + @staticmethod + def _set_blob_uri( + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + blob_uri: str, + ) -> None: + """Set ``blob_uri`` on the evaluator version's definition. + + :param evaluator_version: The evaluator version object to update. + :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] + :param blob_uri: The blob URI to set on the definition. + :type blob_uri: str + """ + if isinstance(evaluator_version, dict): + definition = evaluator_version.get("definition", {}) + if isinstance(definition, dict): + definition["blob_uri"] = blob_uri + elif isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + elif isinstance(evaluator_version, EvaluatorVersion): + definition = evaluator_version.definition + if isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + + async def _start_pending_upload_and_get_container_client( + self, + name: str, + version: str, + connection_name: Optional[str] = None, + ) -> Tuple[ContainerClient, str, str]: + """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. + + :param name: The evaluator name. + :type name: str + :param version: The evaluator version. + :type version: str + :param connection_name: Optional storage account connection name. + :type connection_name: Optional[str] + :return: A tuple of (ContainerClient, version, blob_uri). + :rtype: Tuple[ContainerClient, str, str] + """ + + request_body: dict = {} + if connection_name: + request_body["connectionName"] = connection_name + + pending_upload_response = await self.pending_upload( + name=name, + version=version, + pending_upload_request=request_body, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, + ) + + # The service returns blobReferenceForConsumption + blob_ref = pending_upload_response.get("blobReferenceForConsumption") + if not blob_ref: + raise ValueError("Blob reference is not present in the pending upload response") + + credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None + if not credential: + raise ValueError("SAS credential is not present in the pending upload response") + + sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None + if not sas_uri: + raise ValueError("SAS URI is missing or empty in the pending upload response") + + blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None + if not blob_uri: + raise ValueError("Blob URI is missing or empty in the pending upload response") + + return ( + ContainerClient.from_container_url(container_url=sas_uri), + version, + blob_uri, + ) + + async def _get_next_version(self, name: str) -> str: + """Get the next version number for an evaluator by fetching existing versions. + + :param name: The evaluator name. + :type name: str + :return: The next version number as a string. + :rtype: str + """ + try: + versions = [] + async for v in self.list_versions( + name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} + ): + versions.append(v) + if versions: + numeric_versions = [] + for v in versions: + ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) + if ver and ver.isdigit(): + numeric_versions.append(int(ver)) + if numeric_versions: + return str(max(numeric_versions) + 1) + return "1" + except ResourceNotFoundError: + return "1" + + @distributed_trace_async + async def upload( + self, + name: str, + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + *, + folder: str, + connection_name: Optional[str] = None, + **kwargs: Any, + ) -> EvaluatorVersion: + """Upload all files in a folder to blob storage and create a code-based evaluator version + that references the uploaded code. + + This method calls startPendingUpload to get a SAS URI, uploads files from the folder + to blob storage, then creates an evaluator version referencing the uploaded blob. + + The version is automatically determined by incrementing the latest existing version. + + :param name: The name of the evaluator. Required. + :type name: str + :param evaluator_version: The evaluator version definition. This is the same object accepted + by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, + IO[bytes]. Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :keyword folder: Path to the folder containing the evaluator Python code. Required. + :paramtype folder: str + :keyword connection_name: The name of an Azure Storage Account connection where the files + should be uploaded. If not specified, the default Azure Storage Account connection will be + used. Optional. + :paramtype connection_name: str + :return: The created evaluator version. + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + path_folder = Path(folder) + if not path_folder.exists(): + raise ValueError(f"The provided folder `{folder}` does not exist.") + if path_folder.is_file(): + raise ValueError("The provided path is a file, not a folder.") + + version = await self._get_next_version(name) + logger.info("[upload] Auto-resolved version to '%s'.", version) + + # Get SAS URI via startPendingUpload + container_client, _, blob_uri = await self._start_pending_upload_and_get_container_client( + name=name, + version=version, + connection_name=connection_name, + ) + + async with container_client: + await self._upload_folder_to_blob(container_client, folder, **kwargs) + self._set_blob_uri(evaluator_version, blob_uri) + + result = await self.create_version( + name=name, + evaluator_version=evaluator_version, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, + ) + + return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py new file mode 100644 index 000000000000..856d3433a6a7 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py @@ -0,0 +1,379 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Optional, Any, overload, IO, cast +from openai.types.responses import ResponseInputParam +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.polling import AsyncNoPolling +from azure.core.utils import case_insensitive_dict +from ... import models as _models +from ...models import ( + MemoryStoreOperationUsage, + ResponseUsageInputTokensDetails, + ResponseUsageOutputTokensDetails, + MemoryStoreUpdateCompletedResult, + AsyncUpdateMemoriesLROPoller, +) +from ...models._patch import ( + _AsyncUpdateMemoriesLROPollingMethod, + _FOUNDRY_FEATURES_HEADER_NAME, + _BETA_OPERATION_FEATURE_HEADERS, +) +from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations +from ...operations._patch_memories import _serialize_memory_input_items +from ..._validation import api_version_validation +from ..._utils.model_base import _deserialize + + +class BetaMemoryStoresOperations(GenerateBetaMemoryStoresOperations): + + @overload + async def search_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[Union[str, ResponseInputParam]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, + **kwargs: Any, + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages used to extract relevant memories. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_search_id: The unique ID of the previous search request, enabling incremental + memory search from where the last operation left off. Default value is None. + :paramtype previous_search_id: str + :keyword options: Memory search options. Default value is None. + :paramtype options: ~azure.ai.projects.models.MemorySearchOptions + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def search_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def search_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + async def search_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[Union[str, ResponseInputParam]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, + **kwargs: Any, + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages used to extract relevant memories. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_search_id: The unique ID of the previous search request, enabling incremental + memory search from where the last operation left off. Default value is None. + :paramtype previous_search_id: str + :keyword options: Memory search options. Default value is None. + :paramtype options: ~azure.ai.projects.models.MemorySearchOptions + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + return await super()._search_memories( + name=name, + body=body, + scope=scope, + items=_serialize_memory_input_items(items), + previous_search_id=previous_search_id, + options=options, + **kwargs, + ) + + @overload + async def begin_update_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[Union[str, ResponseInputParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages you would like to store in memory. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_update_memories( + self, + name: str, + body: JSON, + *, + content_type: str = "application/json", + **kwargs: Any, + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_update_memories( + self, + name: str, + body: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any, + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + method_added_on="v1", + params_added_on={"v1": ["api_version", "name", "content_type", "accept"]}, + api_versions_list=["v1"], + ) + async def begin_update_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[Union[str, ResponseInputParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> AsyncUpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages you would like to store in memory. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of AsyncLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) + polling = kwargs.pop("polling", True) + if not isinstance(polling, bool): + raise TypeError("polling must be of type bool.") + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._update_memories_initial( + name=name, + body=body, + scope=scope, + items=_serialize_memory_input_items(items), + previous_update_id=previous_update_id, + update_delay=update_delay, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs, + ) + await raw_result.http_response.read() # type: ignore + + raw_result.http_response.status_code = 202 # type: ignore + raw_result.http_response.headers["Operation-Location"] = ( # type: ignore + f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=v1" # type: ignore + ) + + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) + if deserialized is None: + usage = MemoryStoreOperationUsage( + embedding_tokens=0, + input_tokens=0, + input_tokens_details=ResponseUsageInputTokensDetails(cached_tokens=0), + output_tokens=0, + output_tokens_details=ResponseUsageOutputTokensDetails(reasoning_tokens=0), + total_tokens=0, + ) + deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling: + polling_method: _AsyncUpdateMemoriesLROPollingMethod = _AsyncUpdateMemoriesLROPollingMethod( + lro_delay, + path_format_arguments=path_format_arguments, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, + **kwargs, + ) + else: + polling_method = cast(_AsyncUpdateMemoriesLROPollingMethod, AsyncNoPolling()) + + if cont_token: + return AsyncUpdateMemoriesLROPoller.from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + + return AsyncUpdateMemoriesLROPoller( + self._client, + raw_result, # type: ignore[possibly-undefined] + get_long_running_output, + polling_method, # pylint: disable=possibly-used-before-assignment + ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py new file mode 100644 index 000000000000..7c50fb95ca96 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py @@ -0,0 +1,75 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Optional, AsyncIterable +from azure.core.exceptions import ResourceNotFoundError +from azure.core.tracing.decorator_async import distributed_trace_async + +from ...models._models import ( + Connection, + ApiKeyCredentials, +) +from ...models._enums import ConnectionType + + +class TelemetryOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.aio.AIProjectClient`'s + :attr:`telemetry` attribute. + """ + + _connection_string: Optional[str] = None + + def __init__(self, outer_instance: "azure.ai.projects.aio.AIProjectClient") -> None: # type: ignore[name-defined] + self._outer_instance = outer_instance + + @distributed_trace_async + async def get_application_insights_connection_string(self) -> str: # pylint: disable=name-too-long + """Get the Application Insights connection string associated with the Project's Application Insights resource. + + :return: The Application Insights connection string if a the resource was enabled for the Project. + :rtype: str + :raises ~azure.core.exceptions.ResourceNotFoundError: An Application Insights connection does not + exist for this Foundry project. + """ + if not self._connection_string: + + # TODO: Two REST APIs calls can be replaced by one if we have had REST API for get_with_credentials(connection_type=ConnectionType.APPLICATION_INSIGHTS) + # Returns an empty Iterable if no connections exits. + connections: AsyncIterable[Connection] = self._outer_instance.connections.list( + connection_type=ConnectionType.APPLICATION_INSIGHTS, + ) + + # Note: there can't be more than one AppInsights connection. + connection_name: Optional[str] = None + async for connection in connections: + connection_name = connection.name + break + if not connection_name: + raise ResourceNotFoundError("No Application Insights connection found.") + + connection = ( + await self._outer_instance.connections._get_with_credentials( # pylint: disable=protected-access + name=connection_name + ) + ) + + if isinstance(connection.credentials, ApiKeyCredentials): + if not connection.credentials.api_key: + raise ValueError("Application Insights connection does not have a connection string.") + self._connection_string = connection.credentials.api_key + else: + raise ValueError("Application Insights connection does not use API Key credentials.") + + return self._connection_string diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index eaf5ee2de82e..2c528dea5d0d 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -550,8 +550,8 @@ class RankerVersionType(str, Enum, metaclass=CaseInsensitiveEnumMeta): AUTO = "auto" """AUTO.""" - DEFAULT2024_11_15 = "default-2024-11-15" - """DEFAULT2024_11_15.""" + DEFAULT_2024_11_15 = "default-2024-11-15" + """DEFAULT_2024_11_15.""" class RecurrenceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): @@ -666,8 +666,8 @@ class ToolChoiceParamType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """WEB_SEARCH_PREVIEW.""" COMPUTER_USE_PREVIEW = "computer_use_preview" """COMPUTER_USE_PREVIEW.""" - WEB_SEARCH_PREVIEW2025_03_11 = "web_search_preview_2025_03_11" - """WEB_SEARCH_PREVIEW2025_03_11.""" + WEB_SEARCH_PREVIEW_2025_03_11 = "web_search_preview_2025_03_11" + """WEB_SEARCH_PREVIEW_2025_03_11.""" IMAGE_GENERATION = "image_generation" """IMAGE_GENERATION.""" CODE_INTERPRETER = "code_interpreter" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index f70f9d97a8d9..c9a06e9dacfa 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -8635,7 +8635,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): or a Literal["required"] type. :vartype mode: str or str :ivar tools: A list of tool definitions that the model should be allowed to call. For the - Responses API, the list of tool definitions might look like: + Responses API, the list of tool definitions might look like the following. Required. .. code-block:: json @@ -8643,7 +8643,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): { "type": "function", "name": "get_weather" }, { "type": "mcp", "server_label": "deepwiki" }, { "type": "image_generation" } - ]. Required. + ] :vartype tools: list[dict[str, any]] """ @@ -8656,7 +8656,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): Literal[\"required\"] type.""" tools: list[dict[str, Any]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of tool definitions that the model should be allowed to call. For the Responses API, the - list of tool definitions might look like: + list of tool definitions might look like the following. Required. .. code-block:: json @@ -8664,7 +8664,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): { \"type\": \"function\", \"name\": \"get_weather\" }, { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, { \"type\": \"image_generation\" } - ]. Required.""" + ]""" @overload def __init__( @@ -8933,12 +8933,12 @@ class ToolChoiceWebSearchPreview20250311(ToolChoiceParam, discriminator="web_sea """Indicates that the model should use a built-in tool to generate a response. `Learn more about built-in tools `_. - :ivar type: Required. WEB_SEARCH_PREVIEW2025_03_11. - :vartype type: str or ~azure.ai.projects.models.WEB_SEARCH_PREVIEW2025_03_11 + :ivar type: Required. WEB_SEARCH_PREVIEW_2025_03_11. + :vartype type: str or ~azure.ai.projects.models.WEB_SEARCH_PREVIEW_2025_03_11 """ - type: Literal[ToolChoiceParamType.WEB_SEARCH_PREVIEW2025_03_11] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore - """Required. WEB_SEARCH_PREVIEW2025_03_11.""" + type: Literal[ToolChoiceParamType.WEB_SEARCH_PREVIEW_2025_03_11] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. WEB_SEARCH_PREVIEW_2025_03_11.""" @overload def __init__( @@ -8954,7 +8954,7 @@ def __init__(self, mapping: Mapping[str, Any]) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.type = ToolChoiceParamType.WEB_SEARCH_PREVIEW2025_03_11 # type: ignore + self.type = ToolChoiceParamType.WEB_SEARCH_PREVIEW_2025_03_11 # type: ignore class ToolDescription(_Model): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index 87676c65a8f0..658e4c90fb2f 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -1,15 +1,355 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------- +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +from typing import Final, FrozenSet, List, Dict, Mapping, Optional, Any, Tuple +from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod +from azure.core.polling.base_polling import ( + LROBasePolling, + OperationFailed, + _raise_if_bad_http_status_and_method, +) +from azure.core.polling.async_base_polling import AsyncLROBasePolling +from ._models import CustomCredential as CustomCredentialGenerated +from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult +from ._enums import _FoundryFeaturesOptInKeys -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level +_FOUNDRY_FEATURES_HEADER_NAME: Final[str] = "Foundry-Features" +"""The HTTP header name used to opt in to Foundry preview features.""" + +_BETA_OPERATION_FEATURE_HEADERS: Final[dict] = { + "evaluation_taxonomies": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "evaluators": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, + "insights": _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, + "memory_stores": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, + "red_teams": _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, + "schedules": _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, + "toolsets": _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, +} +"""Foundry-Features header values keyed by beta sub-client property name.""" + + +def _has_header_case_insensitive(headers: Any, header_name: str) -> bool: + """Return True if headers already contains the provided header name. + + :param headers: The headers mapping to search. + :type headers: Any + :param header_name: The header name to look for (case-insensitive). + :type header_name: str + :return: True if the header is present, False otherwise. + :rtype: bool + """ + try: + header_name_lower = header_name.lower() + return any(str(key).lower() == header_name_lower for key in headers) + except Exception: # pylint: disable=broad-except + return False + + +class CustomCredential(CustomCredentialGenerated, discriminator="CustomKeys"): + """Custom credential definition. + + :ivar type: The credential type. Always equals CredentialType.CUSTOM. Required. + :vartype type: str or ~azure.ai.projects.models.CredentialType + :ivar credential_keys: The secret custom credential keys. Required. + :vartype credential_keys: dict[str, str] + """ + + credential_keys: Dict[str, str] + """The secret custom credential keys. Required.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + # Fix for GitHub issue https://github.com/Azure/azure-sdk-for-net/issues/52355 + # Although the issue was filed on C# Projects SDK, the same problem exists in Python SDK. + # Assume your Foundry project has a connection of type `Custom`, named "test_custom_connection", + # and you defined two public and two secrete (private) keys. When you get the connection, the response + # payload will look something like this: + # { + # "name": "test_custom_connection", + # "id": "/subscriptions/.../connections/test_custom_connection", + # "type": "CustomKeys", + # "target": "_", + # "isDefault": true, + # "credentials": { + # "nameofprivatekey1": "PrivateKey1", + # "nameofprivatekey2": "PrivateKey2", + # "type": "CustomKeys" + # }, + # "metadata": { + # "NameOfPublicKey1": "PublicKey1", + # "NameOfPublicKey2": "PublicKey2" + # } + # } + # We would like to add a new Dict property on the Python `credentials` object, named `credential_keys`, + # to hold all the secret keys. This is done by the line below. + if args and isinstance(args[0], Mapping): + self.credential_keys = {k: v for k, v in args[0].items() if k != "type"} + else: + self.credential_keys = {} + + +_FINISHED: Final[FrozenSet[str]] = frozenset(["completed", "superseded", "failed"]) +_FAILED: Final[FrozenSet[str]] = frozenset(["failed"]) + + +class _UpdateMemoriesLROPollingMethod(LROBasePolling): + """A custom polling method implementation for Memory Store updates.""" + + @property + def _current_body(self) -> MemoryStoreUpdateResult: + try: + return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) + except Exception: # pylint: disable=broad-exception-caught + return MemoryStoreUpdateResult() # type: ignore[call-overload] + + def finished(self) -> bool: + """Is this polling finished? + + :return: True/False for whether polling is complete. + :rtype: bool + """ + return self._finished(self.status()) + + @staticmethod + def _finished(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FINISHED + + @staticmethod + def _failed(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FAILED + + def get_continuation_token(self) -> str: + return self._current_body.update_id + + # pylint: disable=arguments-differ + def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] + try: + client = kwargs["client"] + except KeyError as exc: + raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc + + try: + deserialization_callback = kwargs["deserialization_callback"] + except KeyError as exc: + raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc + + return client, continuation_token, deserialization_callback + + def _poll(self) -> None: + """Poll status of operation so long as operation is incomplete and + we have an endpoint to query. + + :raises: OperationFailed if operation status 'Failed' or 'Canceled'. + :raises: BadStatus if response status invalid. + :raises: BadResponse if response invalid. + """ + + if not self.finished(): + self.update_status() + while not self.finished(): + self._delay() + self.update_status() + + if self._failed(self.status()): + raise OperationFailed("Operation failed or canceled") + + final_get_url = self._operation.get_final_get_url(self._pipeline_response) + if final_get_url: + self._pipeline_response = self.request_status(final_get_url) + _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) + + +class _AsyncUpdateMemoriesLROPollingMethod(AsyncLROBasePolling): + """A custom polling method implementation for Memory Store updates.""" + + @property + def _current_body(self) -> MemoryStoreUpdateResult: + try: + return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) + except Exception: # pylint: disable=broad-exception-caught + return MemoryStoreUpdateResult() # type: ignore[call-overload] + + def finished(self) -> bool: + """Is this polling finished? + + :return: True/False for whether polling is complete. + :rtype: bool + """ + return self._finished(self.status()) + + @staticmethod + def _finished(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FINISHED + + @staticmethod + def _failed(status) -> bool: + if hasattr(status, "value"): + status = status.value + return str(status).lower() in _FAILED + + def get_continuation_token(self) -> str: + return self._current_body.update_id + + # pylint: disable=arguments-differ + def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] + try: + client = kwargs["client"] + except KeyError as exc: + raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc + + try: + deserialization_callback = kwargs["deserialization_callback"] + except KeyError as exc: + raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc + + return client, continuation_token, deserialization_callback + + async def _poll(self) -> None: + """Poll status of operation so long as operation is incomplete and + we have an endpoint to query. + + :raises: OperationFailed if operation status 'Failed' or 'Canceled'. + :raises: BadStatus if response status invalid. + :raises: BadResponse if response invalid. + """ + + if not self.finished(): + await self.update_status() + while not self.finished(): + await self._delay() + await self.update_status() + + if self._failed(self.status()): + raise OperationFailed("Operation failed or canceled") + + final_get_url = self._operation.get_final_get_url(self._pipeline_response) + if final_get_url: + self._pipeline_response = await self.request_status(final_get_url) + _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) + + +class UpdateMemoriesLROPoller(LROPoller[MemoryStoreUpdateCompletedResult]): + """Custom LROPoller for Memory Store update operations.""" + + _polling_method: "_UpdateMemoriesLROPollingMethod" + + @property + def update_id(self) -> str: + """Returns the update ID associated with the long-running update memories operation. + + :return: Returns the update ID. + :rtype: str + """ + return self._polling_method._current_body.update_id # pylint: disable=protected-access + + @property + def superseded_by(self) -> Optional[str]: + """Returns the ID of the operation that superseded this update. + + :return: Returns the ID of the superseding operation, if it exists. + :rtype: Optional[str] + """ + return ( + self._polling_method._current_body.superseded_by # pylint: disable=protected-access + if self._polling_method._current_body # pylint: disable=protected-access + else None + ) + + @classmethod + def from_continuation_token( + cls, polling_method: PollingMethod[MemoryStoreUpdateCompletedResult], continuation_token: str, **kwargs: Any + ) -> "UpdateMemoriesLROPoller": + """Create a poller from a continuation token. + + :param polling_method: The polling strategy to adopt + :type polling_method: ~azure.core.polling.PollingMethod + :param continuation_token: An opaque continuation token + :type continuation_token: str + :return: An instance of UpdateMemoriesLROPoller + :rtype: UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. + """ + ( + client, + initial_response, + deserialization_callback, + ) = polling_method.from_continuation_token(continuation_token, **kwargs) + + return cls(client, initial_response, deserialization_callback, polling_method) + + +class AsyncUpdateMemoriesLROPoller(AsyncLROPoller[MemoryStoreUpdateCompletedResult]): + """Custom AsyncLROPoller for Memory Store update operations.""" + + _polling_method: "_AsyncUpdateMemoriesLROPollingMethod" + + @property + def update_id(self) -> str: + """Returns the update ID associated with the long-running update memories operation. + + :return: Returns the update ID. + :rtype: str + """ + return self._polling_method._current_body.update_id # pylint: disable=protected-access + + @property + def superseded_by(self) -> Optional[str]: + """Returns the ID of the operation that superseded this update. + + :return: Returns the ID of the superseding operation, if it exists. + :rtype: Optional[str] + """ + return ( + self._polling_method._current_body.superseded_by # pylint: disable=protected-access + if self._polling_method._current_body # pylint: disable=protected-access + else None + ) + + @classmethod + def from_continuation_token( + cls, + polling_method: AsyncPollingMethod[MemoryStoreUpdateCompletedResult], + continuation_token: str, + **kwargs: Any, + ) -> "AsyncUpdateMemoriesLROPoller": + """Create a poller from a continuation token. + + :param polling_method: The polling strategy to adopt + :type polling_method: ~azure.core.polling.PollingMethod + :param continuation_token: An opaque continuation token + :type continuation_token: str + :return: An instance of AsyncUpdateMemoriesLROPoller + :rtype: AsyncUpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. + """ + ( + client, + initial_response, + deserialization_callback, + ) = polling_method.from_continuation_token(continuation_token, **kwargs) + + return cls(client, initial_response, deserialization_callback, polling_method) + + +__all__: List[str] = [ + "CustomCredential", + "UpdateMemoriesLROPoller", + "AsyncUpdateMemoriesLROPoller", +] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index d5b154dc719b..f320a58a57b0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -4799,7 +4799,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5229,7 +5232,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5329,7 +5335,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6382,7 +6391,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7050,7 +7062,7 @@ def _search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -7136,7 +7148,7 @@ def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items_property": items, + "items": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, @@ -7583,7 +7595,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7927,7 +7942,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( @@ -8227,7 +8245,10 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params + "GET", + urllib.parse.urljoin(next_link, _parsed_next_link.path), + params=_next_request_params, + headers=_headers, ) path_format_arguments = { "endpoint": self._serialize.url( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index 87676c65a8f0..2330bf816c59 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -1,15 +1,142 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------- +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +from functools import wraps +import inspect +from typing import Any, Callable, List +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _BETA_OPERATION_FEATURE_HEADERS, _has_header_case_insensitive +from ._patch_agents import AgentsOperations +from ._patch_datasets import DatasetsOperations +from ._patch_evaluation_rules import EvaluationRulesOperations +from ._patch_evaluators import BetaEvaluatorsOperations +from ._patch_telemetry import TelemetryOperations +from ._patch_connections import ConnectionsOperations +from ._patch_memories import BetaMemoryStoresOperations +from ._operations import ( + BetaEvaluationTaxonomiesOperations, + BetaInsightsOperations, + BetaOperations as GeneratedBetaOperations, + BetaRedTeamsOperations, + BetaSchedulesOperations, + BetaToolsetsOperations, +) -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level + +def _method_accepts_keyword_headers(method: Callable[..., Any]) -> bool: + try: + signature = inspect.signature(method) + except (TypeError, ValueError): + return False + + for parameter in signature.parameters.values(): + if parameter.name == "headers": + return True + if parameter.kind == inspect.Parameter.VAR_KEYWORD: + return True + + return False + + +class _OperationMethodHeaderProxy: + """Proxy that injects the Foundry-Features header into public operation method calls.""" + + def __init__(self, operation: Any, foundry_features_value: str): + object.__setattr__(self, "_operation", operation) + object.__setattr__(self, "_foundry_features_value", foundry_features_value) + + def __getattr__(self, name: str) -> Any: + attribute = getattr(self._operation, name) + + if name.startswith("_") or not callable(attribute) or not _method_accepts_keyword_headers(attribute): + return attribute + + @wraps(attribute) + def _wrapped(*args: Any, **kwargs: Any) -> Any: + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value} + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + try: + headers[_FOUNDRY_FEATURES_HEADER_NAME] = self._foundry_features_value + except Exception: # pylint: disable=broad-except + # Fall back to replacing invalid/immutable header containers. + kwargs["headers"] = { + _FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value, + } + + return attribute(*args, **kwargs) + + return _wrapped + + def __dir__(self) -> list: + return dir(self._operation) + + def __setattr__(self, name: str, value: Any) -> None: + setattr(self._operation, name, value) + + +class BetaOperations(GeneratedBetaOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`beta` attribute. + """ + + evaluation_taxonomies: BetaEvaluationTaxonomiesOperations + """:class:`~azure.ai.projects.operations.BetaEvaluationTaxonomiesOperations` operations""" + evaluators: BetaEvaluatorsOperations + """:class:`~azure.ai.projects.operations.BetaEvaluatorsOperations` operations""" + insights: BetaInsightsOperations + """:class:`~azure.ai.projects.operations.BetaInsightsOperations` operations""" + memory_stores: BetaMemoryStoresOperations + """:class:`~azure.ai.projects.operations.BetaMemoryStoresOperations` operations""" + red_teams: BetaRedTeamsOperations + """:class:`~azure.ai.projects.operations.BetaRedTeamsOperations` operations""" + schedules: BetaSchedulesOperations + """:class:`~azure.ai.projects.operations.BetaSchedulesOperations` operations""" + toolsets: BetaToolsetsOperations + """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + # Replace with patched class that includes upload() + self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) + # Replace with patched class that includes begin_update_memories + self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) + + for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): + setattr( + self, + property_name, + _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), + ) + + +__all__: List[str] = [ + "AgentsOperations", + "BetaEvaluationTaxonomiesOperations", + "BetaEvaluatorsOperations", + "BetaInsightsOperations", + "BetaMemoryStoresOperations", + "BetaOperations", + "BetaRedTeamsOperations", + "BetaSchedulesOperations", + "BetaToolsetsOperations", + "ConnectionsOperations", + "DatasetsOperations", + "EvaluationRulesOperations", + "TelemetryOperations", +] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py new file mode 100644 index 000000000000..8c19e6279729 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py @@ -0,0 +1,218 @@ +# pylint: disable=line-too-long,useless-suppression,pointless-string-statement +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Optional, Any, IO, overload, Final +from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator import distributed_trace +from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset +from .. import models as _models +from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive + +""" +Example service response payload when the caller is trying to use a feature preview without opt-in flag (service error 403 (Forbidden)): + +"error": { + "code": "preview_feature_required", + "message": "Workflow agents is in preview. This operation requires the following opt-in preview feature(s): WorkflowAgents=V1Preview. Include the 'Foundry-Features: WorkflowAgents=V1Preview' header in your request.", + "param": "Foundry-Features", + "type": "invalid_request_error", + "details": [], + "additionalInfo": { + "request_id": "fdbc95804b7599404973026cd9ec732a" + } + } + +""" +_PREVIEW_FEATURE_REQUIRED_CODE: Final = "preview_feature_required" +_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE: Final = ( + '\n**Python SDK users**: This operation requires you to set "allow_preview=True" ' + "when calling the AIProjectClient constructor. " + "\nNote that preview features are under development and subject to change." +) +_AGENT_OPERATION_FEATURE_HEADERS: Final[str] = ",".join( + [ + _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, + _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, + _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, + ] +) + + +class AgentsOperations(GeneratedAgentsOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`agents` attribute. + """ + + @overload + def create_version( + self, + agent_name: str, + *, + definition: _models.AgentDefinition, + content_type: str = "application/json", + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_version( + self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_version( + self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @distributed_trace + def create_version( + self, + agent_name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + definition: _models.AgentDefinition = _Unset, + metadata: Optional[dict[str, str]] = None, + description: Optional[str] = None, + **kwargs: Any, + ) -> _models.AgentVersionDetails: + """Create a new agent version. + + :param agent_name: The unique name that identifies the agent. Name can be used to + retrieve/update/delete the agent. + + * Must start and end with alphanumeric characters, + * Can contain hyphens in the middle + * Must not exceed 63 characters. Required. + :type agent_name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple + agent definition. Required. + :paramtype definition: ~azure.ai.projects.models.AgentDefinition + :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be + useful for storing additional information about the object in a structured + format, and querying for objects via API or the dashboard. + + Keys are strings with a maximum length of 64 characters. Values are strings + with a maximum length of 512 characters. Default value is None. + :paramtype metadata: dict[str, str] + :keyword description: A human-readable description of the agent. Default value is None. + :paramtype description: str + :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.AgentVersionDetails + :raises ~azure.core.exceptions.HttpResponseError: + """ + + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS + kwargs["headers"] = headers + + try: + return super().create_version( + agent_name, + body, + definition=definition, + metadata=metadata, + description=description, + **kwargs, + ) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py new file mode 100644 index 000000000000..581d13671516 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py @@ -0,0 +1,63 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Optional, Any, Union +from azure.core.tracing.decorator import distributed_trace +from ._operations import ConnectionsOperations as ConnectionsOperationsGenerated +from ..models._models import Connection +from ..models._enums import ConnectionType + + +class ConnectionsOperations(ConnectionsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`connections` attribute. + """ + + @distributed_trace + def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: + """Get a connection by name. + + :param name: The name of the connection. Required. + :type name: str + :keyword include_credentials: Whether to include credentials in the response. Default is False. + :paramtype include_credentials: bool + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ~azure.core.exceptions.HttpResponseError: + """ + if include_credentials: + return super()._get_with_credentials(name, **kwargs) + + return super()._get(name, **kwargs) + + @distributed_trace + def get_default( + self, connection_type: Union[str, ConnectionType], *, include_credentials: Optional[bool] = False, **kwargs: Any + ) -> Connection: + """Get the default connection for a given connection type. + + :param connection_type: The type of the connection. Required. + :type connection_type: str or ~azure.ai.projects.models.ConnectionType + :keyword include_credentials: Whether to include credentials in the response. Default is False. + :paramtype include_credentials: bool + :return: Connection. The Connection is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.Connection + :raises ValueError: If no default connection is found for the given type. + :raises ~azure.core.exceptions.HttpResponseError: + """ + connections = super().list(connection_type=connection_type, default_connection=True, **kwargs) + for connection in connections: + return self.get(connection.name, include_credentials=include_credentials, **kwargs) + raise ValueError(f"No default connection found for type: {connection_type}.") diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py new file mode 100644 index 000000000000..bf2c0db51271 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py @@ -0,0 +1,222 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +import os +import re +import logging +from typing import Any, Tuple, Optional +from pathlib import Path +from urllib.parse import urlsplit +from azure.storage.blob import ContainerClient +from azure.core.tracing.decorator import distributed_trace +from ._operations import DatasetsOperations as DatasetsOperationsGenerated +from ..models._models import ( + FileDatasetVersion, + FolderDatasetVersion, + PendingUploadRequest, + PendingUploadResponse, + PendingUploadType, +) + +logger = logging.getLogger(__name__) + + +class DatasetsOperations(DatasetsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`datasets` attribute. + """ + + # Internal helper method to create a new dataset and return a ContainerClient from azure-storage-blob package, + # to the dataset's blob storage. + def _create_dataset_and_get_its_container_client( + self, + name: str, + input_version: str, + connection_name: Optional[str] = None, + ) -> Tuple[ContainerClient, str]: + + pending_upload_response: PendingUploadResponse = self.pending_upload( + name=name, + version=input_version, + pending_upload_request=PendingUploadRequest( + pending_upload_type=PendingUploadType.BLOB_REFERENCE, + connection_name=connection_name, + ), + ) + output_version: str = input_version + + if not pending_upload_response.blob_reference: + raise ValueError("Blob reference is not present") + if not pending_upload_response.blob_reference.credential: + raise ValueError("SAS credential are not present") + if not pending_upload_response.blob_reference.credential.sas_uri: + raise ValueError("SAS URI is missing or empty") + + # For overview on Blob storage SDK in Python see: + # https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python + # https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-python + + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-from-container-url + return ( + ContainerClient.from_container_url( + container_url=pending_upload_response.blob_reference.credential.sas_uri # Of the form: "https://.blob.core.windows.net/?" + ), + output_version, + ) + + @distributed_trace + def upload_file( + self, *, name: str, version: str, file_path: str, connection_name: Optional[str] = None, **kwargs: Any + ) -> FileDatasetVersion: + """Upload file to a blob storage, and create a dataset that references this file. + This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package + to upload the file. Any keyword arguments provided will be passed to the `upload_blob` method. + + :keyword name: The name of the dataset. Required. + :paramtype name: str + :keyword version: The version identifier for the dataset. Required. + :paramtype version: str + :keyword file_path: The file name (including optional path) to be uploaded. Required. + :paramtype file_path: str + :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. + If not specified, the default Azure Storage Account connection will be used. Optional. + :paramtype connection_name: str + :return: The created dataset version. + :rtype: ~azure.ai.projects.models.FileDatasetVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + + pathlib_file_path = Path(file_path) + if not pathlib_file_path.exists(): + raise ValueError(f"The provided file `{file_path}` does not exist.") + if pathlib_file_path.is_dir(): + raise ValueError("The provided file is actually a folder. Use method `upload_folder` instead") + + container_client, output_version = self._create_dataset_and_get_its_container_client( + name=name, input_version=version, connection_name=connection_name + ) + + with container_client: + + with open(file=file_path, mode="rb") as data: + + blob_name = pathlib_file_path.name # Extract the file name from the path. + logger.debug( + "[upload_file] Start uploading file `%s` as blob `%s`.", + file_path, + blob_name, + ) + + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-upload-blob + with container_client.upload_blob(name=blob_name, data=data, **kwargs) as blob_client: + logger.debug("[upload_file] Done uploading") + + # Remove the SAS token from the URL (remove all query strings). + # The resulting format should be "https://.blob.core.windows.net//" + data_uri = urlsplit(blob_client.url)._replace(query="").geturl() + + dataset_version = self.create_or_update( + name=name, + version=output_version, + dataset_version=FileDatasetVersion( + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#azure-storage-blob-blobclient-url + # Per above doc the ".url" contains SAS token... should this be stripped away? + data_uri=data_uri, + ), + ) + + return dataset_version # type: ignore + + @distributed_trace + def upload_folder( + self, + *, + name: str, + version: str, + folder: str, + connection_name: Optional[str] = None, + file_pattern: Optional[re.Pattern] = None, + **kwargs: Any, + ) -> FolderDatasetVersion: + """Upload all files in a folder and its sub folders to a blob storage, while maintaining + relative paths, and create a dataset that references this folder. + This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package + to upload each file. Any keyword arguments provided will be passed to the `upload_blob` method. + + :keyword name: The name of the dataset. Required. + :paramtype name: str + :keyword version: The version identifier for the dataset. Required. + :paramtype version: str + :keyword folder: The folder name (including optional path) to be uploaded. Required. + :paramtype folder: str + :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. + If not specified, the default Azure Storage Account connection will be used. Optional. + :paramtype connection_name: str + :keyword file_pattern: A regex pattern to filter files to be uploaded. Only files matching the pattern + will be uploaded. Optional. + :paramtype file_pattern: re.Pattern + :return: The created dataset version. + :rtype: ~azure.ai.projects.models.FolderDatasetVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + path_folder = Path(folder) + if not Path(path_folder).exists(): + raise ValueError(f"The provided folder `{folder}` does not exist.") + if Path(path_folder).is_file(): + raise ValueError("The provided folder is actually a file. Use method `upload_file` instead.") + + container_client, output_version = self._create_dataset_and_get_its_container_client( + name=name, + input_version=version, + connection_name=connection_name, + ) + + with container_client: + + # Recursively traverse all files in the folder + files_uploaded: bool = False + for root, _, files in os.walk(folder): + for file in files: + if file_pattern and not file_pattern.search(file): + continue # Skip files that do not match the pattern + file_path = os.path.join(root, file) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") # Ensure correct format for Azure + logger.debug( + "[upload_folder] Start uploading file `%s` as blob `%s`.", + file_path, + blob_name, + ) + with open(file=file_path, mode="rb") as data: # Open the file for reading in binary mode + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-upload-blob + container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + logger.debug("[upload_folder] Done uploading file") + files_uploaded = True + logger.debug("[upload_folder] Done uploaded.") + + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + # Remove the SAS token from the URL (remove all query strings). + # The resulting format should be "https://.blob.core.windows.net/" + # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-url + data_uri = urlsplit(container_client.url)._replace(query="").geturl() + + dataset_version = self.create_or_update( + name=name, + version=output_version, + dataset_version=FolderDatasetVersion(data_uri=data_uri), + ) + + return dataset_version # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py new file mode 100644 index 000000000000..19dcee1ed6bd --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py @@ -0,0 +1,130 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Any, IO, overload +from azure.core.exceptions import HttpResponseError +from azure.core.tracing.decorator import distributed_trace +from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON +from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE +from .. import models as _models +from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive + + +class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`evaluation_rules` attribute. + """ + + @overload + def create_or_update( + self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_or_update( + self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @overload + def create_or_update( + self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Required. + :type evaluation_rule: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + ... + + @distributed_trace + def create_or_update( + self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any + ) -> _models.EvaluationRule: + """Create or update an evaluation rule. + + :param id: Unique identifier for the evaluation rule. Required. + :type id: str + :param evaluation_rule: Evaluation rule resource. Is one of the following types: + EvaluationRule, JSON, IO[bytes] Required. + :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] + :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.EvaluationRule + :raises ~azure.core.exceptions.HttpResponseError: + """ + + if getattr(self._config, "allow_preview", False): + # Add Foundry-Features header if not already present + headers = kwargs.get("headers") + if headers is None: + kwargs["headers"] = { + _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + } + elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): + headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + kwargs["headers"] = headers + + try: + return super().create_or_update(id, evaluation_rule, **kwargs) + except HttpResponseError as exc: + if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: + api_error_response = exc.model + if hasattr(api_error_response, "error") and api_error_response.error is not None: + if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: + new_exc = HttpResponseError( + message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", + ) + new_exc.status_code = exc.status_code + new_exc.reason = exc.reason + new_exc.response = exc.response + new_exc.model = exc.model + raise new_exc from exc + raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py new file mode 100644 index 000000000000..3f1c38d97b2b --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py @@ -0,0 +1,258 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +import os +import logging +from typing import Any, Final, IO, Tuple, Optional, Union +from pathlib import Path +from urllib.parse import urlsplit +from azure.storage.blob import ContainerClient +from azure.core.tracing.decorator import distributed_trace +from azure.core.exceptions import HttpResponseError, ResourceNotFoundError +from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON +from ..models._enums import _FoundryFeaturesOptInKeys +from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME +from ..models._models import ( + CodeBasedEvaluatorDefinition, + EvaluatorVersion, +) + +logger = logging.getLogger(__name__) + +_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value + + +class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`beta.evaluators` attribute. + """ + + @staticmethod + def _upload_folder_to_blob( + container_client: ContainerClient, + folder: str, + **kwargs: Any, + ) -> None: + """Walk *folder* and upload every eligible file to the blob container. + + Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` + directories and ``.pyc`` / ``.pyo`` files. + + :param container_client: The blob container client to upload files to. + :type container_client: ~azure.storage.blob.ContainerClient + :param folder: Path to the local folder containing files to upload. + :type folder: str + :raises ValueError: If the folder contains no uploadable files. + :raises HttpResponseError: Re-raised with a friendlier message on + ``AuthorizationPermissionMismatch``. + """ + skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} + skip_extensions = {".pyc", ".pyo"} + files_uploaded = False + + for root, dirs, files in os.walk(folder): + dirs[:] = [d for d in dirs if d not in skip_dirs] + for file_name in files: + if any(file_name.endswith(ext) for ext in skip_extensions): + continue + file_path = os.path.join(root, file_name) + blob_name = os.path.relpath(file_path, folder).replace("\\", "/") + logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) + with open(file=file_path, mode="rb") as data: + try: + container_client.upload_blob(name=str(blob_name), data=data, **kwargs) + except HttpResponseError as e: + if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": + storage_account = urlsplit(container_client.url).hostname + raise HttpResponseError( + message=( + f"Failed to upload file '{blob_name}' to blob storage: " + f"permission denied. Ensure the identity that signed the SAS token " + f"has the 'Storage Blob Data Contributor' role on the storage account " + f"'{storage_account}'. " + f"Original error: {e.message}" + ), + response=e.response, + ) from e + raise + logger.debug("[upload] Done uploading file") + files_uploaded = True + + logger.debug("[upload] Done uploading all files.") + if not files_uploaded: + raise ValueError("The provided folder is empty.") + + @staticmethod + def _set_blob_uri( + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + blob_uri: str, + ) -> None: + """Set ``blob_uri`` on the evaluator version's definition. + + :param evaluator_version: The evaluator version object to update. + :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] + :param blob_uri: The blob URI to set on the definition. + :type blob_uri: str + """ + if isinstance(evaluator_version, dict): + definition = evaluator_version.get("definition", {}) + if isinstance(definition, dict): + definition["blob_uri"] = blob_uri + elif isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + elif isinstance(evaluator_version, EvaluatorVersion): + definition = evaluator_version.definition + if isinstance(definition, CodeBasedEvaluatorDefinition): + definition.blob_uri = blob_uri + + def _start_pending_upload_and_get_container_client( + self, + name: str, + version: str, + connection_name: Optional[str] = None, + ) -> Tuple[ContainerClient, str, str]: + """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. + + :param name: The evaluator name. + :type name: str + :param version: The evaluator version. + :type version: str + :param connection_name: Optional storage account connection name. + :type connection_name: Optional[str] + :return: A tuple of (ContainerClient, version, blob_uri). + :rtype: Tuple[ContainerClient, str, str] + """ + + request_body: dict = {} + if connection_name: + request_body["connectionName"] = connection_name + + pending_upload_response = self.pending_upload( + name=name, + version=version, + pending_upload_request=request_body, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, + ) + + # The service returns blobReferenceForConsumption + blob_ref = pending_upload_response.get("blobReferenceForConsumption") + if not blob_ref: + raise ValueError("Blob reference is not present in the pending upload response") + + credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None + if not credential: + raise ValueError("SAS credential is not present in the pending upload response") + + sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None + if not sas_uri: + raise ValueError("SAS URI is missing or empty in the pending upload response") + + blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None + if not blob_uri: + raise ValueError("Blob URI is missing or empty in the pending upload response") + + return ( + ContainerClient.from_container_url(container_url=sas_uri), + version, + blob_uri, + ) + + def _get_next_version(self, name: str) -> str: + """Get the next version number for an evaluator by fetching existing versions. + + :param name: The evaluator name. + :type name: str + :return: The next version number as a string. + :rtype: str + """ + try: + versions = list( + self.list_versions( + name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} + ) + ) + if versions: + numeric_versions = [] + for v in versions: + ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) + if ver and ver.isdigit(): + numeric_versions.append(int(ver)) + if numeric_versions: + return str(max(numeric_versions) + 1) + return "1" + except ResourceNotFoundError: + return "1" + + @distributed_trace + def upload( + self, + name: str, + evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], + *, + folder: str, + connection_name: Optional[str] = None, + **kwargs: Any, + ) -> EvaluatorVersion: + """Upload all files in a folder to blob storage and create a code-based evaluator version + that references the uploaded code. + + This method calls startPendingUpload to get a SAS URI, uploads files from the folder + to blob storage, then creates an evaluator version referencing the uploaded blob. + + The version is automatically determined by incrementing the latest existing version. + + :param name: The name of the evaluator. Required. + :type name: str + :param evaluator_version: The evaluator version definition. This is the same object accepted + by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, + IO[bytes]. Required. + :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] + :keyword folder: Path to the folder containing the evaluator Python code. Required. + :paramtype folder: str + :keyword connection_name: The name of an Azure Storage Account connection where the files + should be uploaded. If not specified, the default Azure Storage Account connection will be + used. Optional. + :paramtype connection_name: str + :return: The created evaluator version. + :rtype: ~azure.ai.projects.models.EvaluatorVersion + :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. + """ + path_folder = Path(folder) + if not path_folder.exists(): + raise ValueError(f"The provided folder `{folder}` does not exist.") + if path_folder.is_file(): + raise ValueError("The provided path is a file, not a folder.") + + version = self._get_next_version(name) + logger.info("[upload] Auto-resolved version to '%s'.", version) + + # Get SAS URI via startPendingUpload + container_client, _, blob_uri = self._start_pending_upload_and_get_container_client( + name=name, + version=version, + connection_name=connection_name, + ) + + with container_client: + self._upload_folder_to_blob(container_client, folder, **kwargs) + self._set_blob_uri(evaluator_version, blob_uri) + + result = self.create_version( + name=name, + evaluator_version=evaluator_version, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, + ) + + return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py new file mode 100644 index 000000000000..33b932900a35 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py @@ -0,0 +1,414 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Union, Optional, Any, List, overload, IO, cast +from openai.types.responses import ResponseInputParam +from azure.core.tracing.decorator import distributed_trace +from azure.core.polling import NoPolling +from azure.core.utils import case_insensitive_dict +from .. import models as _models +from ..models import ( + MemoryStoreOperationUsage, + ResponseUsageInputTokensDetails, + ResponseUsageOutputTokensDetails, + MemoryStoreUpdateCompletedResult, + UpdateMemoriesLROPoller, +) +from ..models._patch import ( + _UpdateMemoriesLROPollingMethod, + _FOUNDRY_FEATURES_HEADER_NAME, + _BETA_OPERATION_FEATURE_HEADERS, +) +from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations +from .._validation import api_version_validation +from .._utils.model_base import _deserialize, _serialize + + +def _serialize_memory_input_items( + items: Optional[Union[str, ResponseInputParam]], +) -> Optional[List[dict[str, Any]]]: + """Serialize OpenAI response input items to the payload shape expected by memory APIs. + + :param items: The items to serialize. Can be a plain string or an OpenAI ResponseInputParam. + :type items: Optional[Union[str, openai.types.responses.ResponseInputParam]] + :return: A list of serialized item dictionaries, or None if items is None. + :rtype: Optional[List[dict[str, Any]]] + """ + + if items is None: + return None + + if isinstance(items, str): + return [{"role": "user", "type": "message", "content": items}] + + if not isinstance(items, list): + raise TypeError("items must serialize to a list of dictionaries.") + + serialized_items: List[dict[str, Any]] = [] + for item in items: + if hasattr(item, "model_dump"): + item = cast(Any, item).model_dump() + elif hasattr(item, "as_dict"): + item = cast(Any, item).as_dict() + + serialized_item = _serialize(item) + if not isinstance(serialized_item, dict): + raise TypeError("items must serialize to a dictionary .") + serialized_items.append(serialized_item) + return serialized_items + + +class BetaMemoryStoresOperations(GenerateBetaMemoryStoresOperations): + + # A message or list of messages to store in memory. When using a list, each item needs to correspond to a dictionary with `role`, `content` and `type` properties (with type equals `message`). For example: {\"role\": \"user\", \"type\": \"message\", \"content\": \"my user message\"}" + @overload + def search_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[Union[str, ResponseInputParam]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, + **kwargs: Any, + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages used to extract relevant memories. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_search_id: The unique ID of the previous search request, enabling incremental + memory search from where the last operation left off. Default value is None. + :paramtype previous_search_id: str + :keyword options: Memory search options. Default value is None. + :paramtype options: ~azure.ai.projects.models.MemorySearchOptions + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def search_memories( + self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def search_memories( + self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + def search_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[Union[str, ResponseInputParam]] = None, + previous_search_id: Optional[str] = None, + options: Optional[_models.MemorySearchOptions] = None, + **kwargs: Any, + ) -> _models.MemoryStoreSearchResult: + """Search for relevant memories from a memory store based on conversation context. + + :param name: The name of the memory store to search. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages used to extract relevant memories. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_search_id: The unique ID of the previous search request, enabling incremental + memory search from where the last operation left off. Default value is None. + :paramtype previous_search_id: str + :keyword options: Memory search options. Default value is None. + :paramtype options: ~azure.ai.projects.models.MemorySearchOptions + :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping + :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + return super()._search_memories( + name=name, + body=body, + scope=scope, + items=_serialize_memory_input_items(items), + previous_search_id=previous_search_id, + options=options, + **kwargs, + ) + + @overload + def begin_update_memories( + self, + name: str, + *, + scope: str, + content_type: str = "application/json", + items: Optional[Union[str, ResponseInputParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages you would like to store in memory. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_update_memories( + self, + name: str, + body: JSON, + *, + content_type: str = "application/json", + **kwargs: Any, + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: JSON + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_update_memories( + self, + name: str, + body: IO[bytes], + *, + content_type: str = "application/json", + **kwargs: Any, + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Required. + :type body: IO[bytes] + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + method_added_on="v1", + params_added_on={"v1": ["api_version", "name", "content_type", "accept"]}, + api_versions_list=["v1"], + ) + def begin_update_memories( + self, + name: str, + body: Union[JSON, IO[bytes]] = _Unset, + *, + scope: str = _Unset, + items: Optional[Union[str, ResponseInputParam]] = None, + previous_update_id: Optional[str] = None, + update_delay: Optional[int] = None, + **kwargs: Any, + ) -> UpdateMemoriesLROPoller: + """Update memory store with conversation memories. + + :param name: The name of the memory store to update. Required. + :type name: str + :param body: Is either a JSON type or a IO[bytes] type. Required. + :type body: JSON or IO[bytes] + :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. + Required. + :paramtype scope: str + :keyword items: A message or list of messages you would like to store in memory. When using a + list, each item needs to correspond to a dictionary with `role`, `content` and `type` + keys. For example: {"role": "user", "type": "message", "content": "my user message"}. + Only messages with `type` equals `message` are currently processed. Others are ignored. + Default value is None. + :paramtype items: Union[str, openai.types.responses.ResponseInputParam] + :keyword previous_update_id: The unique ID of the previous update request, enabling incremental + memory updates from where the last operation left off. Default value is None. + :paramtype previous_update_id: str + :keyword update_delay: Timeout period before processing the memory update in seconds. + If a new update request is received during this period, it will cancel the current request and + reset the timeout. + Set to 0 to immediately trigger the update without delay. + Defaults to 300 (5 minutes). Default value is None. + :paramtype update_delay: int + :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The + MemoryStoreUpdateCompletedResult is compatible with MutableMapping + :rtype: + ~azure.ai.projects.models.UpdateMemoriesLROPoller + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) + polling = kwargs.pop("polling", True) + if not isinstance(polling, bool): + raise TypeError("polling must be of type bool.") + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._update_memories_initial( + name=name, + body=body, + scope=scope, + items=_serialize_memory_input_items(items), + previous_update_id=previous_update_id, + update_delay=update_delay, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs, + ) + raw_result.http_response.read() # type: ignore + + raw_result.http_response.status_code = 202 # type: ignore + raw_result.http_response.headers["Operation-Location"] = ( # type: ignore + f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=v1" # type: ignore + ) + + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) + if deserialized is None: + usage = MemoryStoreOperationUsage( + embedding_tokens=0, + input_tokens=0, + input_tokens_details=ResponseUsageInputTokensDetails(cached_tokens=0), + output_tokens=0, + output_tokens_details=ResponseUsageOutputTokensDetails(reasoning_tokens=0), + total_tokens=0, + ) + deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling: + polling_method: _UpdateMemoriesLROPollingMethod = _UpdateMemoriesLROPollingMethod( + lro_delay, + path_format_arguments=path_format_arguments, + headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, + **kwargs, + ) + else: + polling_method = cast(_UpdateMemoriesLROPollingMethod, NoPolling()) + + if cont_token: + return UpdateMemoriesLROPoller.from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + + return UpdateMemoriesLROPoller( + self._client, + raw_result, # type: ignore[possibly-undefined] + get_long_running_output, + polling_method, # pylint: disable=possibly-used-before-assignment + ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py new file mode 100644 index 000000000000..158506d22235 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py @@ -0,0 +1,69 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + +from typing import Optional, Iterable +from azure.core.exceptions import ResourceNotFoundError +from azure.core.tracing.decorator import distributed_trace +from ..models._models import Connection, ApiKeyCredentials +from ..models._enums import ConnectionType + + +class TelemetryOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.projects.AIProjectClient`'s + :attr:`telemetry` attribute. + """ + + _connection_string: Optional[str] = None + + def __init__(self, outer_instance: "azure.ai.projects.AIProjectClient") -> None: # type: ignore[name-defined] + self._outer_instance = outer_instance + + @distributed_trace + def get_application_insights_connection_string(self) -> str: # pylint: disable=name-too-long + """Get the Application Insights connection string associated with the Project's Application Insights resource. + + :return: The Application Insights connection string if a the resource was enabled for the Project. + :rtype: str + :raises ~azure.core.exceptions.ResourceNotFoundError: An Application Insights connection does not + exist for this Foundry project. + """ + if not self._connection_string: + + # TODO: Two REST APIs calls can be replaced by one if we have had REST API for get_with_credentials(connection_type=ConnectionType.APPLICATION_INSIGHTS) + # Returns an empty Iterable if no connections exits. + connections: Iterable[Connection] = self._outer_instance.connections.list( + connection_type=ConnectionType.APPLICATION_INSIGHTS, + ) + + # Note: there can't be more than one AppInsights connection. + connection_name: Optional[str] = None + for connection in connections: + connection_name = connection.name + break + if not connection_name: + raise ResourceNotFoundError("No Application Insights connection found.") + + connection = self._outer_instance.connections._get_with_credentials( # pylint: disable=protected-access + name=connection_name + ) + + if isinstance(connection.credentials, ApiKeyCredentials): + if not connection.credentials.api_key: + raise ValueError("Application Insights connection does not have a connection string.") + self._connection_string = connection.credentials.api_key + else: + raise ValueError("Application Insights connection does not use API Key credentials.") + + return self._connection_string diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py new file mode 100644 index 000000000000..4c2b14d84727 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py @@ -0,0 +1,12 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from ._ai_project_instrumentor import AIProjectInstrumentor +from ._trace_function import trace_function + +__all__ = ["AIProjectInstrumentor", "trace_function"] diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py new file mode 100644 index 000000000000..1a22ca314704 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py @@ -0,0 +1,1535 @@ +# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks,docstring-missing-param,docstring-should-be-keyword +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import copy +import functools +import importlib +import json +import logging +import os +from datetime import datetime +from enum import Enum +from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING +from urllib.parse import urlparse +from azure.ai.projects.models._models import Tool +from azure.core import CaseInsensitiveEnumMeta # type: ignore +from azure.core.settings import settings +from azure.core.tracing import AbstractSpan +from ._utils import ( + AGENTS_PROVIDER, + ERROR_TYPE, + GEN_AI_AGENT_DESCRIPTION, + GEN_AI_AGENT_ID, + GEN_AI_AGENT_NAME, + GEN_AI_EVENT_CONTENT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_MESSAGE_ID, + GEN_AI_MESSAGE_STATUS, + GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_PROVIDER_NAME, + GEN_AI_SYSTEM_MESSAGE, + GEN_AI_SYSTEM_INSTRUCTION_EVENT, + GEN_AI_THREAD_ID, + GEN_AI_THREAD_RUN_ID, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_RUN_STEP_START_TIMESTAMP, + GEN_AI_RUN_STEP_END_TIMESTAMP, + GEN_AI_RUN_STEP_STATUS, + GEN_AI_AGENT_VERSION, + GEN_AI_AGENT_HOSTED_CPU, + GEN_AI_AGENT_HOSTED_MEMORY, + GEN_AI_AGENT_HOSTED_IMAGE, + GEN_AI_AGENT_HOSTED_PROTOCOL, + GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION, + AGENT_TYPE_PROMPT, + AGENT_TYPE_WORKFLOW, + AGENT_TYPE_HOSTED, + AGENT_TYPE_UNKNOWN, + GEN_AI_AGENT_TYPE, + ERROR_MESSAGE, + OperationName, + start_span, + _get_use_message_events, +) +from ._responses_instrumentor import _ResponsesInstrumentorPreview + +_Unset: Any = object() + +logger = logging.getLogger(__name__) + +try: + # pylint: disable = no-name-in-module + from opentelemetry.trace import Span, StatusCode + + _tracing_library_available = True +except ModuleNotFoundError: + _tracing_library_available = False + +if TYPE_CHECKING: + from .. import _types + +__all__ = [ + "AIProjectInstrumentor", +] + +_projects_traces_enabled: bool = False +_trace_agents_content: bool = False +_trace_context_propagation_enabled: bool = False +_trace_context_baggage_propagation_enabled: bool = False + + +def _inject_trace_context_sync(request): + """Synchronous event hook to inject trace context (traceparent) into outgoing requests. + + :param request: The httpx Request object. + :type request: httpx.Request + """ + try: + from opentelemetry import propagate + + carrier = dict(request.headers) + propagate.inject(carrier) + for key, value in carrier.items(): + key_lower = key.lower() + # Always include traceparent and tracestate + # Only include baggage if explicitly enabled + if key_lower in ("traceparent", "tracestate"): + if key_lower not in [h.lower() for h in request.headers.keys()]: + request.headers[key] = value + elif key_lower == "baggage" and _trace_context_baggage_propagation_enabled: + if key_lower not in [h.lower() for h in request.headers.keys()]: + request.headers[key] = value + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to inject trace context: %s", e) + + +async def _inject_trace_context_async(request): + """Async event hook to inject trace context (traceparent) into outgoing requests. + + :param request: The httpx Request object. + :type request: httpx.Request + """ + try: + from opentelemetry import propagate + + carrier = dict(request.headers) + propagate.inject(carrier) + for key, value in carrier.items(): + key_lower = key.lower() + # Always include traceparent and tracestate + # Only include baggage if explicitly enabled + if key_lower in ("traceparent", "tracestate"): + if key_lower not in [h.lower() for h in request.headers.keys()]: + request.headers[key] = value + elif key_lower == "baggage" and _trace_context_baggage_propagation_enabled: + if key_lower not in [h.lower() for h in request.headers.keys()]: + request.headers[key] = value + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to inject trace context: %s", e) + + +def _enable_trace_propagation_for_openai_client(openai_client): + """Enable trace context propagation for an OpenAI client. + + This function hooks into the httpx client used by the OpenAI SDK to inject + trace context headers (traceparent, tracestate) into outgoing HTTP requests. + This ensures that client-side spans and server-side spans share the same trace ID. + + :param openai_client: The OpenAI client instance. + :type openai_client: Any + """ + try: + # Access the underlying httpx client + if hasattr(openai_client, "_client"): + httpx_client = openai_client._client # pylint: disable=protected-access + + # Check if the client has event hooks support + if hasattr(httpx_client, "_event_hooks"): + event_hooks = httpx_client._event_hooks # pylint: disable=protected-access + + # Determine if this is an async client + is_async = hasattr(httpx_client, "__aenter__") + + # Add appropriate hook based on client type + if "request" in event_hooks: + hook_to_add = _inject_trace_context_async if is_async else _inject_trace_context_sync + + # Check if our hook is already registered to avoid duplicates + if hook_to_add not in event_hooks["request"]: + event_hooks["request"].append(hook_to_add) + logger.debug("Enabled trace propagation for %s OpenAI client", "async" if is_async else "sync") + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to enable trace propagation for OpenAI client: %s", e) + + +class TraceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): # pylint: disable=C4747 + """An enumeration class to represent different types of traces.""" + + AGENTS = "Agents" + PROJECT = "Project" + + +class AIProjectInstrumentor: + """ + A class for managing the trace instrumentation of the AIProjectClient. + + This class allows enabling or disabling tracing for AI Projects + and provides functionality to check whether instrumentation is active. + + .. warning:: + GenAI tracing is an experimental preview feature. To use this feature, you must set the + environment variable ``AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`` (case insensitive) + before calling the ``instrument()`` method. Spans, attributes, and events may be modified + in future versions. + + """ + + def __init__(self) -> None: + if not _tracing_library_available: + raise ModuleNotFoundError( + "Azure Core Tracing Opentelemetry is not installed. " + "Please install it using 'pip install azure-core-tracing-opentelemetry'" + ) + # We could support different semantic convention versions from the same library + # and have a parameter that specifies the version to use. + self._impl = _AIAgentsInstrumentorPreview() + self._responses_impl = _ResponsesInstrumentorPreview() + + def instrument( + self, + enable_content_recording: Optional[bool] = None, + enable_trace_context_propagation: Optional[bool] = None, + enable_baggage_propagation: Optional[bool] = None, + ) -> None: + """ + Enable trace instrumentation for AIProjectClient. + + .. warning:: + GenAI tracing is an experimental preview feature. To use this feature, you must set the + environment variable ``AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`` (case insensitive). + If this environment variable is not set or set to any value other than "true", + instrumentation will not be enabled and this method will return immediately without effect. + Spans, attributes, and events may be modified in future versions. + + :param enable_content_recording: Whether content recording is enabled as part + of the traces or not. Content in this context refers to chat message content + and function call tool related function names, function parameter names and + values. `True` will enable content recording, `False` will disable it. If no value + is provided, then the value read from environment variable + OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment + variable is not found, then the value will default to `False`. + Please note that successive calls to instrument will always apply the content + recording value provided with the most recent call to instrument (including + applying the environment variable if no value is provided and defaulting to `False` + if the environment variable is not found), even if instrument was already previously + called without uninstrument being called in between the instrument calls. + :type enable_content_recording: bool, optional + :param enable_trace_context_propagation: Whether to enable automatic trace context propagation + to OpenAI SDK HTTP requests. When enabled, traceparent and tracestate headers will be injected + into requests made by OpenAI clients obtained via get_openai_client(), allowing server-side + spans to be correlated with client-side spans. `True` will enable it, `False` will + disable it. If no value is provided, then the value read from environment variable + AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION is used. If the environment + variable is not found, then the value will default to `True`. + Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after + the change; previously acquired clients are unaffected. + :type enable_trace_context_propagation: bool, optional + :param enable_baggage_propagation: Whether to include baggage headers in trace context propagation. + Only applies when enable_trace_context_propagation is True. `True` will enable baggage propagation, + `False` will disable it. If no value is provided, then the value read from environment variable + AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the environment + variable is not found, then the value will default to `False`. + Note: Baggage may contain sensitive application data. This value is evaluated dynamically on + each request, so changes apply immediately — but only for OpenAI clients that had the trace + context propagation hook registered at acquisition time (i.e. clients obtained via + get_openai_client() while enable_trace_context_propagation was True). Clients acquired when + trace context propagation was disabled will never propagate baggage regardless of this value. + :type enable_baggage_propagation: bool, optional + + """ + # Check experimental feature gate + experimental_enabled = os.environ.get("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", "false") + if experimental_enabled.lower() != "true": + logger.warning( + "GenAI tracing is not enabled. Set environment variable " + "AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true to enable this experimental feature." + ) + return + + self._impl.instrument(enable_content_recording, enable_trace_context_propagation, enable_baggage_propagation) + self._responses_impl.instrument(enable_content_recording) + + def uninstrument(self) -> None: + """ + Remove trace instrumentation for AIProjectClient. + + This method removes any active instrumentation, stopping the tracing + of AIProjectClient methods. + """ + self._impl.uninstrument() + self._responses_impl.uninstrument() + + def is_instrumented(self) -> bool: + """ + Check if trace instrumentation for AIProjectClient is currently enabled. + + :return: True if instrumentation is active, False otherwise. + :rtype: bool + """ + return self._impl.is_instrumented() + + def is_content_recording_enabled(self) -> bool: + """This function gets the content recording value. + + :return: A bool value indicating whether content recording is enabled. + :rtype: bool + """ + return self._impl.is_content_recording_enabled() + + +class _AIAgentsInstrumentorPreview: + # pylint: disable=R0904 + """ + A class for managing the trace instrumentation of AI Agents. + + This class allows enabling or disabling tracing for AI Agents + and provides functionality to check whether instrumentation is active. + + .. note:: + This is a private implementation class. Use the public AIProjectInstrumentor class instead. + """ + + def _str_to_bool(self, s): + if s is None: + return False + return str(s).lower() == "true" + + def instrument( + self, + enable_content_recording: Optional[bool] = None, + enable_trace_context_propagation: Optional[bool] = None, + enable_baggage_propagation: Optional[bool] = None, + ): + """ + Enable trace instrumentation for AI Agents. + + .. note:: + This is a private implementation method. Use AIProjectInstrumentor.instrument() instead. + + :param enable_content_recording: Whether content recording is enabled as part + of the traces or not. Content in this context refers to chat message content + and function call tool related function names, function parameter names and + values. `True` will enable content recording, `False` will disable it. If no value + is provided, then the value read from environment variable + OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment + variable is not found, then the value will default to `False`. + Please note that successive calls to instrument will always apply the content + recording value provided with the most recent call to instrument (including + applying the environment variable if no value is provided and defaulting to `False` + if the environment variable is not found), even if instrument was already previously + called without uninstrument being called in between the instrument calls. + :type enable_content_recording: bool, optional + :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. + `True` will enable it, `False` will disable it. If no value is provided, then the + value read from environment variable AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION + is used. If the environment variable is not found, then the value will default to `True`. + Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after + the change; previously acquired clients are unaffected. + :type enable_trace_context_propagation: bool, optional + :param enable_baggage_propagation: Whether to include baggage in trace context propagation. + Only applies when enable_trace_context_propagation is True. `True` will enable it, `False` + will disable it. If no value is provided, then the value read from environment variable + AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the + environment variable is not found, then the value will default to `False`. + Note: This value is evaluated dynamically on each request, so changes apply immediately — + but only for OpenAI clients that had the trace context propagation hook registered at + acquisition time (i.e. clients obtained via get_openai_client() while + enable_trace_context_propagation was True). Clients acquired when trace context propagation + was disabled will never propagate baggage regardless of this value. + :type enable_baggage_propagation: bool, optional + + """ + if enable_content_recording is None: + + var_value = os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") + enable_content_recording = self._str_to_bool(var_value) + + if enable_trace_context_propagation is None: + var_value = os.environ.get("AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION") + enable_trace_context_propagation = True if var_value is None else self._str_to_bool(var_value) + + if enable_baggage_propagation is None: + var_value = os.environ.get("AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE") + enable_baggage_propagation = self._str_to_bool(var_value) + + if not self.is_instrumented(): + self._instrument_projects( + enable_content_recording, enable_trace_context_propagation, enable_baggage_propagation + ) + else: + self._set_enable_content_recording(enable_content_recording=enable_content_recording) + self._set_enable_trace_context_propagation( + enable_trace_context_propagation=enable_trace_context_propagation + ) + self._set_enable_baggage_propagation(enable_baggage_propagation=enable_baggage_propagation) + + def uninstrument(self): + """ + Disable trace instrumentation for AI Projects. + + This method removes any active instrumentation, stopping the tracing + of AI Projects. + """ + if self.is_instrumented(): + self._uninstrument_projects() + + def is_instrumented(self): + """ + Check if trace instrumentation for AI Projects is currently enabled. + + :return: True if instrumentation is active, False otherwise. + :rtype: bool + """ + return self._is_instrumented() + + def set_enable_content_recording(self, enable_content_recording: bool = False) -> None: + """This function sets the content recording value. + + :param enable_content_recording: Indicates whether tracing of message content should be enabled. + This also controls whether function call tool function names, + parameter names and parameter values are traced. + :type enable_content_recording: bool + """ + self._set_enable_content_recording(enable_content_recording=enable_content_recording) + + def is_content_recording_enabled(self) -> bool: + """This function gets the content recording value. + + :return: A bool value indicating whether content tracing is enabled. + :rtype bool + """ + return self._is_content_recording_enabled() + + def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None: + for attr in attrs: + key, value = attr + if value is not None: + span.add_attribute(key, value) + + def _parse_url(self, url): + parsed = urlparse(url) + server_address = parsed.hostname + port = parsed.port + return server_address, port + + def _remove_function_call_names_and_arguments(self, tool_calls: list) -> list: + tool_calls_copy = copy.deepcopy(tool_calls) + for tool_call in tool_calls_copy: + if "function" in tool_call: + if "name" in tool_call["function"]: + del tool_call["function"]["name"] + if "arguments" in tool_call["function"]: + del tool_call["function"]["arguments"] + if not tool_call["function"]: + del tool_call["function"] + return tool_calls_copy + + def _create_event_attributes( + self, + thread_id: Optional[str] = None, + agent_id: Optional[str] = None, + thread_run_id: Optional[str] = None, + message_id: Optional[str] = None, + message_status: Optional[str] = None, + run_step_status: Optional[str] = None, + created_at: Optional[datetime] = None, + completed_at: Optional[datetime] = None, + cancelled_at: Optional[datetime] = None, + failed_at: Optional[datetime] = None, + run_step_last_error: Optional[Any] = None, + usage: Optional[Any] = None, + ) -> Dict[str, Any]: + attrs: Dict[str, Any] = {GEN_AI_PROVIDER_NAME: AGENTS_PROVIDER} + if thread_id: + attrs[GEN_AI_THREAD_ID] = thread_id + + if agent_id: + attrs[GEN_AI_AGENT_ID] = agent_id + + if thread_run_id: + attrs[GEN_AI_THREAD_RUN_ID] = thread_run_id + + if message_id: + attrs[GEN_AI_MESSAGE_ID] = message_id + + if message_status: + attrs[GEN_AI_MESSAGE_STATUS] = self._status_to_string(message_status) + + if run_step_status: + attrs[GEN_AI_RUN_STEP_STATUS] = self._status_to_string(run_step_status) + + if created_at: + if isinstance(created_at, datetime): + attrs[GEN_AI_RUN_STEP_START_TIMESTAMP] = created_at.isoformat() + else: + # fallback in case integer or string gets passed + attrs[GEN_AI_RUN_STEP_START_TIMESTAMP] = str(created_at) + + end_timestamp = None + if completed_at: + end_timestamp = completed_at + elif cancelled_at: + end_timestamp = cancelled_at + elif failed_at: + end_timestamp = failed_at + + if isinstance(end_timestamp, datetime): + attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = end_timestamp.isoformat() + elif end_timestamp: + # fallback in case int or string gets passed + attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = str(end_timestamp) + + if run_step_last_error: + attrs[ERROR_MESSAGE] = run_step_last_error.message + attrs[ERROR_TYPE] = run_step_last_error.code + + if usage: + attrs[GEN_AI_USAGE_INPUT_TOKENS] = usage.prompt_tokens + attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.completion_tokens + + return attrs + + def add_thread_message_event( + self, + span, + message: Any, + usage: Optional[Any] = None, + ) -> None: + + content_body: Optional[Union[str, Dict[str, Any]]] = None + if _trace_agents_content: + # Handle processed dictionary messages + if isinstance(message, dict): + content = message.get("content") + if content: + content_body = content + + role = "unknown" + if isinstance(message, dict): + role = message.get("role", "unknown") + elif hasattr(message, "role"): + role = getattr(message, "role", "unknown") + + self._add_message_event( + span, + role, + content_body, + attachments=( + message.get("attachments") if isinstance(message, dict) else getattr(message, "attachments", None) + ), + thread_id=(message.get("thread_id") if isinstance(message, dict) else getattr(message, "thread_id", None)), + agent_id=(message.get("agent_id") if isinstance(message, dict) else getattr(message, "agent_id", None)), + message_id=(message.get("id") if isinstance(message, dict) else getattr(message, "id", None)), + thread_run_id=(message.get("run_id") if isinstance(message, dict) else getattr(message, "run_id", None)), + message_status=(message.get("status") if isinstance(message, dict) else getattr(message, "status", None)), + incomplete_details=( + message.get("incomplete_details") + if isinstance(message, dict) + else getattr(message, "incomplete_details", None) + ), + usage=usage, + ) + + def _add_message_event( # pylint: disable=too-many-branches,too-many-statements + self, + span, + role: str, + content: Optional[Union[str, dict[str, Any], List[dict[str, Any]]]] = None, + attachments: Any = None, + thread_id: Optional[str] = None, + agent_id: Optional[str] = None, + message_id: Optional[str] = None, + thread_run_id: Optional[str] = None, + message_status: Optional[str] = None, + incomplete_details: Optional[Any] = None, + usage: Optional[Any] = None, + ) -> None: + # TODO document new fields + + content_array: List[Dict[str, Any]] = [] + if _trace_agents_content: + if isinstance(content, List): + for block in content: + if isinstance(block, Dict): + if block.get("type") == "input_text" and "text" in block: + # Use optimized format with consistent "content" field + content_array.append({"type": "text", "content": block["text"]}) + break + elif content: + # Simple text content + content_array.append({"type": "text", "content": content}) + + if attachments: + # Add attachments as separate content items + for attachment in attachments: + attachment_body: Dict[str, Any] = { + "type": "attachment", + "content": {"id": attachment.file_id}, + } + if attachment.tools: + content_dict: Dict[str, Any] = attachment_body["content"] # type: ignore[assignment] + content_dict["tools"] = [self._get_field(tool, "type") for tool in attachment.tools] + content_array.append(attachment_body) + + # Add metadata fields if present + metadata: Dict[str, Any] = {} + if incomplete_details: + metadata["incomplete_details"] = incomplete_details + + # Combine content array with metadata if needed + event_data: Union[Dict[str, Any], List[Dict[str, Any]]] = {} + if metadata: + # When we have metadata, we need to wrap it differently + event_data = metadata + if content_array: + event_data["content"] = content_array + else: + # No metadata, use content array directly as the event data + event_data = content_array if isinstance(content_array, list) else {} + + use_events = _get_use_message_events() + + if use_events: + # Use events for message tracing + attributes = self._create_event_attributes( + thread_id=thread_id, + agent_id=agent_id, + thread_run_id=thread_run_id, + message_id=message_id, + message_status=message_status, + usage=usage, + ) + # Store as JSON - either array or object depending on metadata + if not metadata and content_array: + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) + else: + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_data, ensure_ascii=False) + + event_name = None + if role == "user": + event_name = "gen_ai.input.message" + elif role == "system": + event_name = "gen_ai.system_instruction" + else: + event_name = "gen_ai.input.message" + + span.span_instance.add_event(name=event_name, attributes=attributes) + else: + # Use attributes for message tracing + # Prepare message content as JSON + message_json = json.dumps( + [{"role": role, "parts": content_array}] if content_array else [{"role": role}], ensure_ascii=False + ) + + # Determine which attribute to use based on role + if role == "user": + attribute_name = GEN_AI_INPUT_MESSAGES + elif role == "assistant": + attribute_name = GEN_AI_OUTPUT_MESSAGES + else: + # Default to input messages for other roles (including system) + attribute_name = GEN_AI_INPUT_MESSAGES + + # Set the attribute on the span + if span and span.span_instance.is_recording: + span.add_attribute(attribute_name, message_json) + + def _get_field(self, obj: Any, field: str) -> Any: + if not obj: + return None + + if isinstance(obj, dict): + return obj.get(field, None) + + return getattr(obj, field, None) + + def _add_instructions_event( + self, + span: "AbstractSpan", + instructions: Optional[str], + additional_instructions: Optional[str], + agent_id: Optional[str] = None, + thread_id: Optional[str] = None, + response_schema: Optional[Any] = None, + ) -> None: + # Early return if no instructions AND no response schema to trace + if not instructions and response_schema is None: + return + + content_array: List[Dict[str, Any]] = [] + + # Add instructions if provided + if instructions: + if _trace_agents_content: + # Combine instructions if both exist + if additional_instructions: + combined_text = f"{instructions} {additional_instructions}" + else: + combined_text = instructions + + # Use optimized format with consistent "content" field + content_array.append({"type": "text", "content": combined_text}) + else: + # Content recording disabled, but indicate that text instructions exist + content_array.append({"type": "text"}) + + # Add response schema if provided + if response_schema is not None: + if _trace_agents_content: + # Convert schema to JSON string if it's a dict/object + if isinstance(response_schema, dict): + schema_str = json.dumps(response_schema, ensure_ascii=False) + elif hasattr(response_schema, "as_dict"): + # Handle model objects that have as_dict() method (e.g., ResponseFormatJsonSchemaSchema) + schema_dict = response_schema.as_dict() + schema_str = json.dumps(schema_dict, ensure_ascii=False) + # TODO: is this 'elif' still needed, not that we added the above? + elif hasattr(response_schema, "__dict__"): + # Handle model objects by converting to dict first + schema_dict = {k: v for k, v in response_schema.__dict__.items() if not k.startswith("_")} + schema_str = json.dumps(schema_dict, ensure_ascii=False) + else: + schema_str = str(response_schema) + + content_array.append({"type": "response_schema", "content": schema_str}) + else: + # Content recording disabled, but indicate that response schema exists + content_array.append({"type": "response_schema"}) + + use_events = _get_use_message_events() + + if use_events: + # Use events for instructions tracing + attributes = self._create_event_attributes(agent_id=agent_id, thread_id=thread_id) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) + span.span_instance.add_event(name=GEN_AI_SYSTEM_INSTRUCTION_EVENT, attributes=attributes) + else: + # Use attributes for instructions tracing + # System instructions format: array of content objects without role/parts wrapper + message_json = json.dumps(content_array, ensure_ascii=False) + if span and span.span_instance.is_recording: + span.add_attribute(GEN_AI_SYSTEM_MESSAGE, message_json) + + def _status_to_string(self, status: Any) -> str: + return status.value if hasattr(status, "value") else status + + @staticmethod + def agent_api_response_to_str(response_format: Any) -> Optional[str]: + """ + Convert response_format to string. + + :param response_format: The response format. + :type response_format: Any + :returns: string for the response_format. + :rtype: Optional[str] + :raises: Value error if response_format is not a supported type. + """ + if isinstance(response_format, str) or response_format is None: + return response_format + raise ValueError(f"Unknown response format {type(response_format)}") + + def start_create_agent_span( # pylint: disable=too-many-locals + self, + server_address: Optional[str] = None, + port: Optional[int] = None, + model: Optional[str] = None, + name: Optional[str] = None, + description: Optional[str] = None, + instructions: Optional[str] = None, + _tools: Optional[List[Tool]] = None, + _tool_resources: Optional[Any] = None, # TODO: Used to be: _tool_resources: Optional[ItemResource] = None, + # _toolset: Optional["ToolSet"] = None, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + response_format: Optional[Any] = None, + reasoning_effort: Optional[str] = None, + reasoning_summary: Optional[str] = None, + text: Optional[Any] = None, # pylint: disable=unused-argument + structured_inputs: Optional[Any] = None, + agent_type: Optional[str] = None, + workflow_yaml: Optional[str] = None, + hosted_cpu: Optional[str] = None, + hosted_memory: Optional[str] = None, + hosted_image: Optional[str] = None, + hosted_protocol: Optional[str] = None, + hosted_protocol_version: Optional[str] = None, + ) -> "Optional[AbstractSpan]": + span = start_span( + OperationName.CREATE_AGENT, + server_address=server_address, + port=port, + span_name=f"{OperationName.CREATE_AGENT.value} {name}", + model=model, + temperature=temperature, + top_p=top_p, + response_format=_AIAgentsInstrumentorPreview.agent_api_response_to_str(response_format), + reasoning_effort=reasoning_effort, + reasoning_summary=reasoning_summary, + structured_inputs=(str(structured_inputs) if structured_inputs is not None else None), + ) + if span and span.span_instance.is_recording: + span.add_attribute(GEN_AI_OPERATION_NAME, OperationName.CREATE_AGENT.value) + if name: + span.add_attribute(GEN_AI_AGENT_NAME, name) + if description: + span.add_attribute(GEN_AI_AGENT_DESCRIPTION, description) + if agent_type: + span.add_attribute(GEN_AI_AGENT_TYPE, agent_type) + + # Add hosted agent specific attributes + if hosted_cpu: + span.add_attribute(GEN_AI_AGENT_HOSTED_CPU, hosted_cpu) + if hosted_memory: + span.add_attribute(GEN_AI_AGENT_HOSTED_MEMORY, hosted_memory) + if hosted_image: + span.add_attribute(GEN_AI_AGENT_HOSTED_IMAGE, hosted_image) + if hosted_protocol: + span.add_attribute(GEN_AI_AGENT_HOSTED_PROTOCOL, hosted_protocol) + if hosted_protocol_version: + span.add_attribute(GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION, hosted_protocol_version) + + # Extract response schema from text parameter if available + response_schema = None + if response_format and text: + # Extract schema from text.format.schema if available + if hasattr(text, "format"): + format_info = getattr(text, "format", None) + if format_info and hasattr(format_info, "schema"): + response_schema = getattr(format_info, "schema", None) + elif isinstance(text, dict): + format_info = text.get("format") + if format_info and isinstance(format_info, dict): + response_schema = format_info.get("schema") + + # Add instructions event (if instructions exist) + self._add_instructions_event(span, instructions, None, response_schema=response_schema) + + # Add workflow event if workflow type agent (always add event, but only include YAML content if content recording enabled) + if workflow_yaml is not None: + # Always create event with empty array or workflow content based on recording setting + if _trace_agents_content: + # Include actual workflow YAML when content recording is enabled + event_body: List[Dict[str, Any]] = [{"type": "workflow", "content": workflow_yaml}] + else: + # Empty array when content recording is disabled (agent type already indicates it's a workflow) + event_body = [] + attributes = self._create_event_attributes() + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False) + span.span_instance.add_event(name="gen_ai.agent.workflow", attributes=attributes) + + return span + + def start_create_thread_span( + self, + server_address: Optional[str] = None, + port: Optional[int] = None, + messages: Optional[List[Dict[str, str]]] = None, + # _tool_resources: Optional["ToolResources"] = None, + ) -> "Optional[AbstractSpan]": + span = start_span(OperationName.CREATE_THREAD, server_address=server_address, port=port) + if span and span.span_instance.is_recording: + for message in messages or []: + self.add_thread_message_event(span, message) + + return span + + def get_server_address_from_arg(self, arg: Any) -> Optional[Tuple[str, Optional[int]]]: + """ + Extracts the base endpoint and port from the provided arguments _config.endpoint attribute, if that exists. + + :param arg: The argument from which the server address is to be extracted. + :type arg: Any + :return: A tuple of (base endpoint, port) or None if endpoint is not found. + :rtype: Optional[Tuple[str, Optional[int]]] + """ + if hasattr(arg, "_config") and hasattr( + arg._config, # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] + "endpoint", + ): + endpoint = ( + arg._config.endpoint # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] + ) + parsed_url = urlparse(endpoint) + return f"{parsed_url.scheme}://{parsed_url.netloc}", parsed_url.port + return None + + def _create_agent_span_from_parameters( + self, *args, **kwargs + ): # pylint: disable=too-many-statements,too-many-locals,too-many-branches,docstring-missing-param + """Extract parameters and create span for create_agent tracing.""" + server_address_info = self.get_server_address_from_arg(args[0]) + server_address = server_address_info[0] if server_address_info else None + port = server_address_info[1] if server_address_info else None + + # Extract parameters from the new nested structure + agent_name = kwargs.get("agent_name") + definition = kwargs.get("definition", {}) + if definition is None: + body = kwargs.get("body", {}) + definition = body.get("definition", {}) + + # Extract parameters from definition + model = definition.get("model") + instructions = definition.get("instructions") + temperature = definition.get("temperature") + top_p = definition.get("top_p") + tools = definition.get("tools") + reasoning = definition.get("reasoning") + text = definition.get("text") + structured_inputs = None + description = definition.get("description") + tool_resources = definition.get("tool_resources") + workflow_yaml = definition.get("workflow") # Extract workflow YAML for workflow agents + # toolset = definition.get("toolset") + + # Extract hosted agent specific attributes + hosted_cpu = definition.get("cpu") + hosted_memory = definition.get("memory") + hosted_image = definition.get("image") + hosted_protocol = None + hosted_protocol_version = None + container_protocol_versions = definition.get("container_protocol_versions") + if container_protocol_versions and len(container_protocol_versions) > 0: + # Extract protocol and version from first entry + protocol_record = container_protocol_versions[0] + if hasattr(protocol_record, "protocol"): + hosted_protocol = getattr(protocol_record, "protocol", None) + elif isinstance(protocol_record, dict): + hosted_protocol = protocol_record.get("protocol") + + if hasattr(protocol_record, "version"): + hosted_protocol_version = getattr(protocol_record, "version", None) + elif isinstance(protocol_record, dict): + hosted_protocol_version = protocol_record.get("version") + + # Determine agent type from definition + # Check for hosted agent first (most specific - has container/image configuration) + agent_type = None + if hosted_image or hosted_cpu or hosted_memory: + agent_type = AGENT_TYPE_HOSTED + elif workflow_yaml: + agent_type = AGENT_TYPE_WORKFLOW + elif instructions or model: + # Prompt agent - identified by having instructions and/or a model. + # Note: An agent with only a model (no instructions) is treated as a prompt agent, + # though this is uncommon. Typically prompt agents have both model and instructions. + agent_type = AGENT_TYPE_PROMPT + else: + # Unknown type - set to "unknown" to indicate we couldn't determine it + agent_type = AGENT_TYPE_UNKNOWN + + # Extract reasoning effort and summary from reasoning if available + reasoning_effort = None + reasoning_summary = None + if reasoning: + # Handle different types of reasoning objects + if hasattr(reasoning, "effort") and hasattr(reasoning, "summary"): + # Azure OpenAI Reasoning model object + reasoning_effort = getattr(reasoning, "effort", None) + reasoning_summary = getattr(reasoning, "summary", None) + elif isinstance(reasoning, dict): + # Dictionary format + reasoning_effort = reasoning.get("effort") + reasoning_summary = reasoning.get("summary") + elif isinstance(reasoning, str): + # Try to parse as JSON if it's a string + try: + reasoning_dict = json.loads(reasoning) + if isinstance(reasoning_dict, dict): + reasoning_effort = reasoning_dict.get("effort") + reasoning_summary = reasoning_dict.get("summary") + except (json.JSONDecodeError, ValueError): + # If parsing fails, treat the whole string as effort + reasoning_effort = reasoning + + # Extract response format from text.format if available + response_format = None + if text: + # Handle different types of text objects + if hasattr(text, "format"): + # Azure AI Agents PromptAgentDefinitionTextOptions model object + format_info = getattr(text, "format", None) + if format_info: + if hasattr(format_info, "type"): + # Format is also a model object + response_format = getattr(format_info, "type", None) + elif isinstance(format_info, dict): + # Format is a dictionary + response_format = format_info.get("type") + elif isinstance(text, dict): + # Dictionary format + format_info = text.get("format") + if format_info and isinstance(format_info, dict): + format_type = format_info.get("type") + if format_type: + response_format = format_type + elif isinstance(text, str): + # Try to parse as JSON if it's a string + try: + text_dict = json.loads(text) + if isinstance(text_dict, dict): + format_info = text_dict.get("format") + if format_info and isinstance(format_info, dict): + format_type = format_info.get("type") + if format_type: + response_format = format_type + except (json.JSONDecodeError, ValueError): + # If parsing fails, ignore + pass + + # Create and return the span + return self.start_create_agent_span( + server_address=server_address, + port=port, + name=agent_name, + model=model, + description=description, + instructions=instructions, + _tools=tools, + _tool_resources=tool_resources, + temperature=temperature, + top_p=top_p, + response_format=response_format, + reasoning_effort=reasoning_effort, + reasoning_summary=reasoning_summary, + text=text, + structured_inputs=structured_inputs, + agent_type=agent_type, + workflow_yaml=workflow_yaml, + hosted_cpu=hosted_cpu, + hosted_memory=hosted_memory, + hosted_image=hosted_image, + hosted_protocol=hosted_protocol, + hosted_protocol_version=hosted_protocol_version, + ) + + def trace_create_agent(self, function, *args, **kwargs): + span = self._create_agent_span_from_parameters(*args, **kwargs) + + if span is None: + return function(*args, **kwargs) + + with span: + try: + result = function(*args, **kwargs) + span.add_attribute(GEN_AI_AGENT_ID, result.id) + span.add_attribute(GEN_AI_AGENT_VERSION, result.version) + except Exception as exc: + self.record_error(span, exc) + raise + + return result + + async def trace_create_agent_async(self, function, *args, **kwargs): + span = self._create_agent_span_from_parameters(*args, **kwargs) + + if span is None: + return await function(*args, **kwargs) + + with span: + try: + result = await function(*args, **kwargs) + span.add_attribute(GEN_AI_AGENT_ID, result.id) + span.add_attribute(GEN_AI_AGENT_VERSION, result.version) + except Exception as exc: + self.record_error(span, exc) + raise + + return result + + def _create_thread_span_from_parameters(self, *args, **kwargs): + """Extract parameters, process messages, and create span for create_thread tracing.""" + server_address_info = self.get_server_address_from_arg(args[0]) + server_address = server_address_info[0] if server_address_info else None + port = server_address_info[1] if server_address_info else None + messages = kwargs.get("messages") + items = kwargs.get("items") + if items is None: + body = kwargs.get("body") + if isinstance(body, dict): + items = body.get("items") + + # Process items if available to extract content from generators + processed_messages = messages + if items: + processed_messages = [] + for item in items: + # Handle model objects like ResponsesUserMessageItemParam, ResponsesSystemMessageItemParam + if hasattr(item, "__dict__"): + final_content = str(getattr(item, "content", "")) + # Create message structure for telemetry + role = getattr(item, "role", "unknown") + processed_messages.append({"role": role, "content": final_content}) + else: + # Handle dict items or simple string items + if isinstance(item, dict): + processed_messages.append(item) + else: + # Handle simple string items + processed_messages.append({"role": "unknown", "content": str(item)}) + + # Create and return the span + return self.start_create_thread_span(server_address=server_address, port=port, messages=processed_messages) + + def trace_create_thread(self, function, *args, **kwargs): + span = self._create_thread_span_from_parameters(*args, **kwargs) + + if span is None: + return function(*args, **kwargs) + + with span: + try: + result = function(*args, **kwargs) + span.add_attribute(GEN_AI_THREAD_ID, result.get("id")) + except Exception as exc: + self.record_error(span, exc) + raise + + return result + + async def trace_create_thread_async(self, function, *args, **kwargs): + span = self._create_thread_span_from_parameters(*args, **kwargs) + + if span is None: + return await function(*args, **kwargs) + + with span: + try: + result = await function(*args, **kwargs) + span.add_attribute(GEN_AI_THREAD_ID, result.get("id")) + except Exception as exc: + self.record_error(span, exc) + raise + + return result + + def trace_list_messages_async(self, function, *args, **kwargs): + """Placeholder method for list messages async tracing. + + The full instrumentation infrastructure for list operations + is not yet implemented, so we simply call the original function. + + :param function: The original function to be called. + :type function: Callable + :param args: Positional arguments passed to the original function. + :type args: tuple + :param kwargs: Keyword arguments passed to the original function. + :type kwargs: dict + :return: The result of calling the original function. + :rtype: Any + """ + return function(*args, **kwargs) + + def trace_list_run_steps_async(self, function, *args, **kwargs): + """Placeholder method for list run steps async tracing. + + The full instrumentation infrastructure for list operations + is not yet implemented, so we simply call the original function. + + :param function: The original function to be called. + :type function: Callable + :param args: Positional arguments passed to the original function. + :type args: tuple + :param kwargs: Keyword arguments passed to the original function. + :type kwargs: dict + :return: The result of calling the original function. + :rtype: Any + """ + return function(*args, **kwargs) + + def _trace_sync_function( + self, + function: Callable, + *, + _args_to_ignore: Optional[List[str]] = None, + _trace_type=TraceType.AGENTS, + _name: Optional[str] = None, + ) -> Callable: + """ + Decorator that adds tracing to a synchronous function. + + :param function: The function to be traced. + :type function: Callable + :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. + :type: args_to_ignore: [List[str]], optional + :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. + :type trace_type: TraceType, optional + :param name: The name of the trace, will set to func name if not provided. + :type name: str, optional + :return: The traced function. + :rtype: Callable + """ + + @functools.wraps(function) + def inner(*args, **kwargs): # pylint: disable=R0911 + span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 + if span_impl_type is None: + return function(*args, **kwargs) + + class_function_name = function.__qualname__ + + if class_function_name.endswith(".create_version") and ("AgentsOperations" in class_function_name): + kwargs.setdefault("merge_span", True) + return self.trace_create_agent(function, *args, **kwargs) + # if class_function_name.startswith("ConversationsOperations.create"): + # kwargs.setdefault("merge_span", True) + # return self.trace_create_thread(function, *args, **kwargs) + return function(*args, **kwargs) # Ensure all paths return + + return inner + + def _trace_async_function( + self, + function: Callable, + *, + _args_to_ignore: Optional[List[str]] = None, + _trace_type=TraceType.AGENTS, + _name: Optional[str] = None, + ) -> Callable: + """ + Decorator that adds tracing to an asynchronous function. + + :param function: The function to be traced. + :type function: Callable + :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. + :type: args_to_ignore: [List[str]], optional + :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. + :type trace_type: TraceType, optional + :param name: The name of the trace, will set to func name if not provided. + :type name: str, optional + :return: The traced function. + :rtype: Callable + """ + + @functools.wraps(function) + async def inner(*args, **kwargs): # pylint: disable=R0911 + span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 + if span_impl_type is None: + return await function(*args, **kwargs) + + class_function_name = function.__qualname__ + + if class_function_name.endswith(".create_version") and ("AgentsOperations" in class_function_name): + kwargs.setdefault("merge_span", True) + return await self.trace_create_agent_async(function, *args, **kwargs) + # if class_function_name.startswith("ConversationOperations.create"): + # kwargs.setdefault("merge_span", True) + # return await self.trace_create_thread_async(function, *args, **kwargs) + return await function(*args, **kwargs) # Ensure all paths return + + return inner + + def _trace_async_list_function( + self, + function: Callable, + *, + _args_to_ignore: Optional[List[str]] = None, + _trace_type=TraceType.AGENTS, + _name: Optional[str] = None, + ) -> Callable: + """ + Decorator that adds tracing to an asynchronous function. + + :param function: The function to be traced. + :type function: Callable + :param args_to_ignore: A list of argument names to be ignored in the trace. + Defaults to None. + :type: args_to_ignore: [List[str]], optional + :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. + :type trace_type: TraceType, optional + :param name: The name of the trace, will set to func name if not provided. + :type name: str, optional + :return: The traced function. + :rtype: Callable + """ + + @functools.wraps(function) + def inner(*args, **kwargs): # pylint: disable=R0911 + span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 + if span_impl_type is None: + return function(*args, **kwargs) + + class_function_name = function.__qualname__ + if class_function_name.startswith("MessagesOperations.list"): + kwargs.setdefault("merge_span", True) + return self.trace_list_messages_async(function, *args, **kwargs) + if class_function_name.startswith("RunStepsOperations.list"): + kwargs.setdefault("merge_span", True) + return self.trace_list_run_steps_async(function, *args, **kwargs) + # Handle the default case (if the function name does not match) + return None # Ensure all paths return + + return inner + + def _inject_async(self, f, _trace_type, _name): + if _name.startswith("list"): + wrapper_fun = self._trace_async_list_function(f) + else: + wrapper_fun = self._trace_async_function(f) + wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] + return wrapper_fun + + def _inject_sync(self, f, _trace_type, _name): + wrapper_fun = self._trace_sync_function(f) + wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] + return wrapper_fun + + def _agents_apis(self): + sync_apis = ( + ( + "azure.ai.projects.operations", + "AgentsOperations", + "create_version", + TraceType.AGENTS, + "create_version", + ), + # ( + # "azure.ai.agents.operations", + # "ConversationsOperations", + # "create", + # TraceType.AGENTS, + # "create", + # ), + ) + async_apis = ( + ( + "azure.ai.projects.aio.operations", + "AgentsOperations", + "create_version", + TraceType.AGENTS, + "create_version", + ), + # ( + # "azure.ai.agents.aio.operations", + # "ConversationsOperations", + # "create", + # TraceType.AGENTS, + # "create", + # ), + ) + return sync_apis, async_apis + + def _project_apis(self): + """Define AIProjectClient APIs to instrument for trace propagation. + + :return: A tuple containing sync and async API tuples. + :rtype: Tuple[Tuple, Tuple] + """ + sync_apis = ( + ( + "azure.ai.projects", + "AIProjectClient", + "get_openai_client", + TraceType.PROJECT, + "get_openai_client", + ), + ) + async_apis = ( + ( + "azure.ai.projects.aio", + "AIProjectClient", + "get_openai_client", + TraceType.PROJECT, + "get_openai_client", + ), + ) + return sync_apis, async_apis + + def _inject_openai_client(self, f, _trace_type, _name): + """Injector for get_openai_client that enables trace context propagation if opted in. + + :return: The wrapped function with trace context propagation enabled. + :rtype: Callable + """ + + @functools.wraps(f) + def wrapper(*args, **kwargs): + openai_client = f(*args, **kwargs) + if _trace_context_propagation_enabled: + _enable_trace_propagation_for_openai_client(openai_client) + return openai_client + + wrapper._original = f # type: ignore # pylint: disable=protected-access + return wrapper + + def _agents_api_list(self): + sync_apis, async_apis = self._agents_apis() + yield sync_apis, self._inject_sync + yield async_apis, self._inject_async + + def _project_api_list(self): + """Generate project API list with custom injector. + + :return: A generator yielding API tuples with injectors. + :rtype: Generator + """ + sync_apis, async_apis = self._project_apis() + yield sync_apis, self._inject_openai_client + yield async_apis, self._inject_openai_client + + def _generate_api_and_injector(self, apis): + for api, injector in apis: + for module_name, class_name, method_name, trace_type, name in api: + try: + module = importlib.import_module(module_name) + api = getattr(module, class_name) + if hasattr(api, method_name): + # The function list is sync in both sync and async classes. + yield api, method_name, trace_type, injector, name + except AttributeError as e: + # Log the attribute exception with the missing class information + logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug + "AttributeError: The module '%s' does not have the class '%s'. %s", + module_name, + class_name, + str(e), + ) + except Exception as e: # pylint: disable=broad-except + # Log other exceptions as a warning, as we are not sure what they might be + logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug + "An unexpected error occurred: '%s'", str(e) + ) + + def _available_projects_apis_and_injectors(self): + """ + Generates a sequence of tuples containing Agents and Project API classes, method names, and + corresponding injector functions. + + :return: A generator yielding tuples. + :rtype: tuple + """ + yield from self._generate_api_and_injector(self._agents_api_list()) + yield from self._generate_api_and_injector(self._project_api_list()) + + def _instrument_projects( + self, + enable_content_tracing: bool = False, + enable_trace_context_propagation: bool = False, + enable_baggage_propagation: bool = False, + ): + """This function modifies the methods of the Projects API classes to + inject logic before calling the original methods. + The original methods are stored as _original attributes of the methods. + + :param enable_content_tracing: Indicates whether tracing of message content should be enabled. + This also controls whether function call tool function names, + parameter names and parameter values are traced. + :type enable_content_tracing: bool + :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. + :type enable_trace_context_propagation: bool + :param enable_baggage_propagation: Whether to include baggage in trace context propagation. + :type enable_baggage_propagation: bool + """ + # pylint: disable=W0603 + global _projects_traces_enabled + global _trace_agents_content + global _trace_context_propagation_enabled + global _trace_context_baggage_propagation_enabled + if _projects_traces_enabled: + raise RuntimeError("Traces already started for AI Agents") + + _projects_traces_enabled = True + _trace_agents_content = enable_content_tracing + _trace_context_propagation_enabled = enable_trace_context_propagation + _trace_context_baggage_propagation_enabled = enable_baggage_propagation + for ( + api, + method, + trace_type, + injector, + name, + ) in self._available_projects_apis_and_injectors(): + # Check if the method of the api class has already been modified + if not hasattr(getattr(api, method), "_original"): + setattr(api, method, injector(getattr(api, method), trace_type, name)) + + def _uninstrument_projects(self): + """This function restores the original methods of the Projects API classes + by assigning them back from the _original attributes of the modified methods. + """ + # pylint: disable=W0603 + global _projects_traces_enabled + global _trace_agents_content + global _trace_context_propagation_enabled + global _trace_context_baggage_propagation_enabled + _trace_agents_content = False + _trace_context_propagation_enabled = False + _trace_context_baggage_propagation_enabled = False + for api, method, _, _, _ in self._available_projects_apis_and_injectors(): + if hasattr(getattr(api, method), "_original"): + setattr(api, method, getattr(getattr(api, method), "_original")) + + _projects_traces_enabled = False + + def _is_instrumented(self): + """This function returns True if Projects API has already been instrumented + for tracing and False if it has not been instrumented. + + :return: A value indicating whether the Projects API is currently instrumented or not. + :rtype: bool + """ + return _projects_traces_enabled + + def _set_enable_content_recording(self, enable_content_recording: bool = False) -> None: + """This function sets the content recording value. + + :param enable_content_recording: Indicates whether tracing of message content should be enabled. + This also controls whether function call tool function names, + parameter names and parameter values are traced. + :type enable_content_recording: bool + """ + global _trace_agents_content # pylint: disable=W0603 + _trace_agents_content = enable_content_recording + + def _is_content_recording_enabled(self) -> bool: + """This function gets the content recording value. + + :return: A bool value indicating whether content tracing is enabled. + :rtype bool + """ + return _trace_agents_content + + def _set_enable_trace_context_propagation(self, enable_trace_context_propagation: bool = False) -> None: + """This function sets the trace context propagation value. + + :param enable_trace_context_propagation: Indicates whether automatic trace context propagation should be enabled. + :type enable_trace_context_propagation: bool + """ + global _trace_context_propagation_enabled # pylint: disable=W0603 + _trace_context_propagation_enabled = enable_trace_context_propagation + + def _set_enable_baggage_propagation(self, enable_baggage_propagation: bool = False) -> None: + """This function sets the baggage propagation value. + + :param enable_baggage_propagation: Indicates whether baggage should be included in trace context propagation. + :type enable_baggage_propagation: bool + """ + global _trace_context_baggage_propagation_enabled # pylint: disable=W0603 + _trace_context_baggage_propagation_enabled = enable_baggage_propagation + + def record_error(self, span, exc): + # Set the span status to error + if isinstance(span.span_instance, Span): # pyright: ignore [reportPossiblyUnboundVariable] + span.span_instance.set_status( + StatusCode.ERROR, # pyright: ignore [reportPossiblyUnboundVariable] + description=str(exc), + ) + module = getattr(exc, "__module__", "") + module = module if module != "builtins" else "" + error_type = f"{module}.{type(exc).__name__}" if module else type(exc).__name__ + self._set_attributes(span, ("error.type", error_type)) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py new file mode 100644 index 000000000000..95cb28183b35 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py @@ -0,0 +1,4883 @@ +# pylint: disable=line-too-long,useless-suppression,too-many-lines +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +# pyright: reportPossiblyUnboundVariable=false +# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks,docstring-missing-param,docstring-should-be-keyword,docstring-missing-return,docstring-missing-rtype,broad-exception-caught,logging-fstring-interpolation,unused-variable,unused-argument,protected-access,global-variable-not-assigned,global-statement +# Pylint disables are appropriate for this internal instrumentation class because: +# - Extensive documentation isn't required for internal methods (docstring-missing-*) +# - Broad exception catching is often necessary for telemetry (shouldn't break user code) +# - Protected access is needed for instrumentation to hook into client internals +# - Some unused variables/arguments exist for API compatibility and future extensibility +# - Global variables are used for metrics state management across instances +# - Line length and complexity limits are relaxed for instrumentation code +import functools +import json +import logging +import os +import time +from enum import Enum +from typing import Any, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING +from urllib.parse import urlparse +from azure.core import CaseInsensitiveEnumMeta # type: ignore +from azure.core.tracing import AbstractSpan +from ._utils import ( + ERROR_TYPE, + GEN_AI_AGENT_ID, + GEN_AI_AGENT_NAME, + GEN_AI_ASSISTANT_MESSAGE_EVENT, + GEN_AI_CLIENT_OPERATION_DURATION, + GEN_AI_CLIENT_TOKEN_USAGE, + GEN_AI_CONVERSATION_ID, + GEN_AI_CONVERSATION_ITEM_EVENT, + GEN_AI_CONVERSATION_ITEM_ID, + GEN_AI_EVENT_CONTENT, + GEN_AI_INPUT_MESSAGES, + GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, + GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT, + GEN_AI_OPERATION_NAME, + GEN_AI_OUTPUT_MESSAGES, + GEN_AI_PROVIDER_NAME, + GEN_AI_REQUEST_MODEL, + GEN_AI_REQUEST_TOOLS, + GEN_AI_RESPONSE_FINISH_REASONS, + GEN_AI_RESPONSE_ID, + GEN_AI_RESPONSE_MODEL, + GEN_AI_TOKEN_TYPE, + GEN_AI_TOOL_MESSAGE_EVENT, + GEN_AI_USAGE_INPUT_TOKENS, + GEN_AI_USAGE_OUTPUT_TOKENS, + GEN_AI_USER_MESSAGE_EVENT, + GEN_AI_WORKFLOW_ACTION_EVENT, + OPERATION_NAME_CHAT, + OPERATION_NAME_INVOKE_AGENT, + OperationName, + SERVER_ADDRESS, + SERVER_PORT, + SPAN_NAME_CHAT, + SPAN_NAME_INVOKE_AGENT, + _get_use_message_events, + _get_use_simple_tool_format, + start_span, + RESPONSES_PROVIDER, +) + +_Unset: Any = object() + +logger = logging.getLogger(__name__) + +try: # pylint: disable=unused-import + # pylint: disable = no-name-in-module + from opentelemetry.trace import StatusCode + from opentelemetry.metrics import get_meter + + _tracing_library_available = True +except ModuleNotFoundError: + _tracing_library_available = False + +if TYPE_CHECKING: + pass + +__all__ = [ + "ResponsesInstrumentor", +] + +_responses_traces_enabled: bool = False +_trace_responses_content: bool = False +_trace_binary_data: bool = False + +# Metrics instruments +_operation_duration_histogram = None +_token_usage_histogram = None + + +class TraceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): # pylint: disable=C4747 + """An enumeration class to represent different types of traces.""" + + RESPONSES = "Responses" + CONVERSATIONS = "Conversations" + + +class ResponsesInstrumentor: + """ + A class for managing the trace instrumentation of OpenAI Responses and Conversations APIs. + + This class allows enabling or disabling tracing for OpenAI Responses and Conversations API calls + and provides functionality to check whether instrumentation is active. + """ + + def __init__(self) -> None: + if not _tracing_library_available: + logger.warning( + "OpenTelemetry is not available. " + "Please install opentelemetry-api and opentelemetry-sdk to enable tracing." + ) + # We could support different semantic convention versions from the same library + # and have a parameter that specifies the version to use. + self._impl = _ResponsesInstrumentorPreview() + + def instrument(self, enable_content_recording: Optional[bool] = None) -> None: + """ + Enable trace instrumentation for OpenAI Responses and Conversations APIs. + + :param enable_content_recording: Whether content recording is enabled as part + of the traces or not. Content in this context refers to chat message content + and function call tool related function names, function parameter names and + values. `True` will enable content recording, `False` will disable it. If no value + is provided, then the value read from environment variable + OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment + variable is not found, then the value will default to `False`. + Please note that successive calls to instrument will always apply the content + recording value provided with the most recent call to instrument (including + applying the environment variable if no value is provided and defaulting to `False` + if the environment variable is not found), even if instrument was already previously + called without uninstrument being called in between the instrument calls. + :type enable_content_recording: bool, optional + """ + self._impl.instrument(enable_content_recording) + + def uninstrument(self) -> None: + """ + Remove trace instrumentation for OpenAI Responses and Conversations APIs. + + This method removes any active instrumentation, stopping the tracing + of OpenAI Responses and Conversations API methods. + """ + self._impl.uninstrument() + + def is_instrumented(self) -> bool: + """ + Check if trace instrumentation for OpenAI Responses and Conversations APIs is currently enabled. + + :return: True if instrumentation is active, False otherwise. + :rtype: bool + """ + return self._impl.is_instrumented() + + def is_content_recording_enabled(self) -> bool: + """This function gets the content recording value. + + :return: A bool value indicating whether content recording is enabled. + :rtype: bool + """ + return self._impl.is_content_recording_enabled() + + def is_binary_data_enabled(self) -> bool: + """This function gets the binary data tracing value. + + :return: A bool value indicating whether binary data tracing is enabled. + :rtype: bool + """ + return self._impl.is_binary_data_enabled() + + def is_simple_tool_format_enabled(self) -> bool: + """This function gets the simple tool format value. + + When enabled, function tool calls use a simplified OTEL-compliant format: + tool_call: {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} + tool_call_response: {"type": "tool_call_response", "id": "...", "result": "..."} + + :return: A bool value indicating whether simple tool format is enabled. + :rtype: bool + """ + return _get_use_simple_tool_format() + + +class _ResponsesInstrumentorPreview: # pylint: disable=too-many-instance-attributes,too-many-statements,too-many-public-methods + """ + A class for managing the trace instrumentation of OpenAI Responses API. + + This class allows enabling or disabling tracing for OpenAI Responses API calls + and provides functionality to check whether instrumentation is active. + """ + + def _str_to_bool(self, s): + if s is None: + return False + return str(s).lower() == "true" + + def _is_instrumentation_enabled(self) -> bool: + """Check if instrumentation is enabled via environment variable. + + Returns True if AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API is not set or is "true" (case insensitive). + Returns False if the environment variable is set to any other value. + """ + env_value = os.environ.get("AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API") + if env_value is None: + return True # Default to enabled if not specified + return str(env_value).lower() == "true" + + def _initialize_metrics(self): + """Initialize OpenTelemetry metrics instruments.""" + global _operation_duration_histogram, _token_usage_histogram # pylint: disable=global-statement + + if not _tracing_library_available: + return + + try: + meter = get_meter(__name__) # pyright: ignore [reportPossiblyUnboundVariable] + + # Operation duration histogram + _operation_duration_histogram = meter.create_histogram( + name=GEN_AI_CLIENT_OPERATION_DURATION, + description="Duration of GenAI operations", + unit="s", + ) + + # Token usage histogram + _token_usage_histogram = meter.create_histogram( + name=GEN_AI_CLIENT_TOKEN_USAGE, + description="Token usage for GenAI operations", + unit="token", + ) + + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to initialize metrics: %s", e) + + def _record_operation_duration( + self, + duration: float, + operation_name: str, + server_address: Optional[str] = None, + port: Optional[int] = None, + model: Optional[str] = None, + error_type: Optional[str] = None, + ): + """Record operation duration metrics.""" + global _operation_duration_histogram # pylint: disable=global-variable-not-assigned + + if not _operation_duration_histogram: + return + + attributes = { + GEN_AI_OPERATION_NAME: operation_name, + GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, + } + + if server_address: + attributes[SERVER_ADDRESS] = server_address + if port: + attributes[SERVER_PORT] = str(port) + if model: + attributes[GEN_AI_REQUEST_MODEL] = model + if error_type: + attributes[ERROR_TYPE] = error_type + + try: + _operation_duration_histogram.record(duration, attributes) + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to record operation duration: %s", e) + + def _record_token_usage( + self, + token_count: int, + token_type: str, + operation_name: str, + server_address: Optional[str] = None, + model: Optional[str] = None, + ): + """Record token usage metrics.""" + global _token_usage_histogram # pylint: disable=global-variable-not-assigned + + if not _token_usage_histogram: + return + + attributes = { + GEN_AI_OPERATION_NAME: operation_name, + GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, + GEN_AI_TOKEN_TYPE: token_type, + } + + if server_address: + attributes[SERVER_ADDRESS] = server_address + if model: + attributes[GEN_AI_REQUEST_MODEL] = model + + try: + _token_usage_histogram.record(token_count, attributes) + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to record token usage: %s", e) + + def _record_token_metrics_from_response( + self, + response: Any, + operation_name: str, + server_address: Optional[str] = None, + model: Optional[str] = None, + ): + """Extract and record token usage metrics from response.""" + try: + if hasattr(response, "usage"): + usage = response.usage + if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: + self._record_token_usage( + usage.prompt_tokens, + "input", + operation_name, + server_address, + model, + ) + if hasattr(usage, "completion_tokens") and usage.completion_tokens: + self._record_token_usage( + usage.completion_tokens, + "completion", + operation_name, + server_address, + model, + ) + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to extract token metrics from response: %s", e) + + def _record_metrics( # pylint: disable=docstring-missing-type + self, + operation_type: str, + duration: float, + result: Any = None, + span_attributes: Optional[Dict[str, Any]] = None, + error_type: Optional[str] = None, + ): + """ + Record comprehensive metrics for different API operation types. + + :param operation_type: Type of operation ("responses", "conversation", "conversation_items") + :param duration: Operation duration in seconds + :param result: API response object for extracting response-specific attributes + :param span_attributes: Dictionary of span attributes to extract relevant metrics from + :param error_type: Error type if an error occurred + """ + try: + # Build base attributes - always included + if operation_type == "responses": + operation_name = "responses" + elif operation_type == "conversation": + operation_name = "create_conversation" + elif operation_type == "conversation_items": + operation_name = "list_conversation_items" + else: + operation_name = operation_type + + # Extract relevant attributes from span_attributes if provided + server_address = None + server_port = None + request_model = None + + if span_attributes: + server_address = span_attributes.get(SERVER_ADDRESS) + server_port = span_attributes.get(SERVER_PORT) + request_model = span_attributes.get(GEN_AI_REQUEST_MODEL) + + # Extract response-specific attributes from result if provided + response_model = None + + if result: + response_model = getattr(result, "model", None) + # service_tier = getattr(result, "service_tier", None) # Unused + + # Use response model if available, otherwise fall back to request model + model_for_metrics = response_model or request_model + + # Record operation duration with relevant attributes + self._record_operation_duration( + duration=duration, + operation_name=operation_name, + server_address=server_address, + port=server_port, + model=model_for_metrics, + error_type=error_type, + ) + + # Record token usage metrics if result has usage information + if result and hasattr(result, "usage"): + usage = result.usage + if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: + self._record_token_usage( + token_count=usage.prompt_tokens, + token_type="input", + operation_name=operation_name, + server_address=server_address, + model=model_for_metrics, + ) + if hasattr(usage, "completion_tokens") and usage.completion_tokens: + self._record_token_usage( + token_count=usage.completion_tokens, + token_type="completion", + operation_name=operation_name, + server_address=server_address, + model=model_for_metrics, + ) + # Handle Responses API specific token fields + if hasattr(usage, "input_tokens") and usage.input_tokens: + self._record_token_usage( + token_count=usage.input_tokens, + token_type="input", + operation_name=operation_name, + server_address=server_address, + model=model_for_metrics, + ) + if hasattr(usage, "output_tokens") and usage.output_tokens: + self._record_token_usage( + token_count=usage.output_tokens, + token_type="completion", + operation_name=operation_name, + server_address=server_address, + model=model_for_metrics, + ) + + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug("Failed to record metrics for %s: %s", operation_type, e) + + def instrument(self, enable_content_recording: Optional[bool] = None): + """ + Enable trace instrumentation for OpenAI Responses API. + + :param enable_content_recording: Whether content recording is enabled as part + of the traces or not. Content in this context refers to chat message content + and function call tool related function names, function parameter names and + values. `True` will enable content recording, `False` will disable it. If no value + is provided, then the value read from environment variable + OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment + variable is not found, then the value will default to `False`. + Please note that successive calls to instrument will always apply the content + recording value provided with the most recent call to instrument (including + applying the environment variable if no value is provided and defaulting to `False` + if the environment variable is not found), even if instrument was already previously + called without uninstrument being called in between the instrument calls. + :type enable_content_recording: bool, optional + """ + # Check if instrumentation is enabled via environment variable + if not self._is_instrumentation_enabled(): + return # No-op if instrumentation is disabled + + if enable_content_recording is None: + enable_content_recording = self._str_to_bool( + os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "false") + ) + + # Check if binary data tracing is enabled + enable_binary_data = self._str_to_bool(os.environ.get("AZURE_TRACING_GEN_AI_INCLUDE_BINARY_DATA", "false")) + + if not self.is_instrumented(): + self._instrument_responses(enable_content_recording, enable_binary_data) + else: + self.set_enable_content_recording(enable_content_recording) + self.set_enable_binary_data(enable_binary_data) + + def uninstrument(self): + """ + Disable trace instrumentation for OpenAI Responses API. + + This method removes any active instrumentation, stopping the tracing + of OpenAI Responses API calls. + """ + if self.is_instrumented(): + self._uninstrument_responses() + + def is_instrumented(self): + """ + Check if trace instrumentation for OpenAI Responses API is currently enabled. + + :return: True if instrumentation is active, False otherwise. + :rtype: bool + """ + return self._is_instrumented() + + def set_enable_content_recording(self, enable_content_recording: bool = False) -> None: + """This function sets the content recording value. + + :param enable_content_recording: Indicates whether tracing of message content should be enabled. + This also controls whether function call tool function names, + parameter names and parameter values are traced. + :type enable_content_recording: bool + """ + self._set_enable_content_recording(enable_content_recording=enable_content_recording) + + def is_content_recording_enabled(self) -> bool: + """This function gets the content recording value. + + :return: A bool value indicating whether content tracing is enabled. + :rtype bool + """ + return self._is_content_recording_enabled() + + def set_enable_binary_data(self, enable_binary_data: bool = False) -> None: + """This function sets the binary data tracing value. + + :param enable_binary_data: Indicates whether tracing of binary data (such as images) should be enabled. + This only takes effect when content recording is also enabled. + :type enable_binary_data: bool + """ + self._set_enable_binary_data(enable_binary_data=enable_binary_data) + + def is_binary_data_enabled(self) -> bool: + """This function gets the binary data tracing value. + + :return: A bool value indicating whether binary data tracing is enabled. + :rtype: bool + """ + return self._is_binary_data_enabled() + + def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None: + for attr in attrs: + span.add_attribute(attr[0], attr[1]) + + def _set_span_attribute_safe(self, span: "AbstractSpan", key: str, value: Any) -> None: + """Safely set a span attribute only if the value is meaningful.""" + if not span or not span.span_instance.is_recording: + return + + # Only set attribute if value exists and is meaningful + if value is not None and value != "" and value != []: + span.add_attribute(key, value) + + def _parse_url(self, url): + parsed = urlparse(url) + server_address = parsed.hostname + port = parsed.port + return server_address, port + + def _create_event_attributes( + self, + conversation_id: Optional[str] = None, # pylint: disable=unused-argument + message_role: Optional[str] = None, + ) -> Dict[str, Any]: + attrs: Dict[str, Any] = {GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER} + # Removed conversation_id from event attributes as requested - it's redundant + # if conversation_id: + # attrs[GEN_AI_CONVERSATION_ID] = conversation_id + # Commented out - message_role is now included in the event content instead + # if message_role: + # attrs[GEN_AI_MESSAGE_ROLE] = message_role + return attrs + + def _append_to_message_attribute( + self, + span: "AbstractSpan", + attribute_name: str, + new_messages: List[Dict[str, Any]], + ) -> None: + """Helper to append messages to an existing attribute, combining with previous messages.""" + # Get existing attribute value + existing_value = span.span_instance.attributes.get(attribute_name) if span.span_instance.attributes else None + + if existing_value: + # Parse existing JSON array + try: + existing_messages = json.loads(existing_value) + if not isinstance(existing_messages, list): + existing_messages = [] + except (json.JSONDecodeError, TypeError): + existing_messages = [] + + # Append new messages + combined_messages = existing_messages + new_messages + else: + # No existing value, just use new messages + combined_messages = new_messages + + # Set the combined value + combined_json = json.dumps(combined_messages, ensure_ascii=False) + span.add_attribute(attribute_name, combined_json) + + def _add_message_event( + self, + span: "AbstractSpan", + role: str, + content: Optional[str] = None, + conversation_id: Optional[str] = None, + finish_reason: Optional[str] = None, + ) -> None: + """Add a message event or attribute to the span based on configuration.""" + content_array: List[Dict[str, Any]] = [] + + # Always include role and finish_reason, only include actual content if tracing is enabled + if content: + parts = [] + if _trace_responses_content: + # Include actual content when tracing is enabled + parts = [{"type": "text", "content": content}] + else: + # When content recording is off but we know there's content, include type-only structure + parts = [{"type": "text"}] + + # Create role object + role_obj: Dict[str, Any] = {"role": role} + + # Always add parts to show content structure + role_obj["parts"] = parts + + # Add finish_reason for assistant messages if available + if role == "assistant" and finish_reason: + role_obj["finish_reason"] = finish_reason + + content_array.append(role_obj) + + # Serialize the content array to JSON + json_content = json.dumps(content_array, ensure_ascii=False) + + if _get_use_message_events(): + # Original event-based implementation + attributes = self._create_event_attributes( + conversation_id=conversation_id, + message_role=role, + ) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json_content + + # Map role to appropriate event name constant + if role == "user": + event_name = GEN_AI_USER_MESSAGE_EVENT + elif role == "assistant": + event_name = GEN_AI_ASSISTANT_MESSAGE_EVENT + else: + # Fallback for any other roles (shouldn't happen in practice) + event_name = f"gen_ai.{role}.message" + + span.span_instance.add_event(name=event_name, attributes=attributes) + else: + # New attribute-based implementation + # Append messages to the appropriate attribute (accumulating multiple messages) + if role in ("user", "tool"): + # User and tool messages go to input.messages + self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) + elif role == "assistant": + # Assistant messages go to output.messages + self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) + + def _add_tool_message_events( # pylint: disable=too-many-branches + self, + span: "AbstractSpan", + tool_outputs: List[Any], + conversation_id: Optional[str] = None, + ) -> None: + """Add tool message events (tool call outputs) to the span.""" + parts: List[Dict[str, Any]] = [] + + # Always iterate through tool_outputs to build type and id metadata + if tool_outputs: + for output_item in tool_outputs: + try: + tool_output: Dict[str, Any] = {} + + # Get the item type - handle both dict and object attributes + if isinstance(output_item, dict): + item_type = output_item.get("type") + else: + item_type = getattr(output_item, "type", None) + + if not item_type: + continue # Skip if no type + + # Use the API type directly + tool_output["type"] = item_type + + # Add call_id as "id" - handle both dict and object + if isinstance(output_item, dict): + call_id = output_item.get("call_id") or output_item.get("id") + else: + call_id = getattr(output_item, "call_id", None) or getattr(output_item, "id", None) + + if call_id: + tool_output["id"] = call_id + + # Add output field only if content recording is enabled + if _trace_responses_content: + # Add output field - parse JSON string if needed + if isinstance(output_item, dict): + output_value = output_item.get("output") + else: + output_value = getattr(output_item, "output", None) + + if output_value is not None: + # Try to parse JSON string into object + if isinstance(output_value, str): + try: + parsed_output = json.loads(output_value) + # For computer_call_output, strip binary data if binary tracing is disabled + if item_type == "computer_call_output" and not _trace_binary_data: + if isinstance(parsed_output, dict): + output_type = parsed_output.get("type") + # Remove image_url from computer_screenshot if binary data tracing is off + if output_type == "computer_screenshot" and "image_url" in parsed_output: + parsed_output = { + k: v for k, v in parsed_output.items() if k != "image_url" + } + tool_output["output"] = parsed_output + except (json.JSONDecodeError, TypeError): + # If parsing fails, keep as string + tool_output["output"] = output_value + else: + # For non-string output, also check for binary data in computer outputs + if item_type == "computer_call_output" and not _trace_binary_data: + if isinstance(output_value, dict): + output_type = output_value.get("type") + if output_type == "computer_screenshot" and "image_url" in output_value: + output_value = {k: v for k, v in output_value.items() if k != "image_url"} + tool_output["output"] = output_value + + # Add to parts array + # Check if simple tool format is enabled for function_call_output types + if _get_use_simple_tool_format() and item_type == "function_call_output": + # Build simplified OTEL-compliant format: + # {"type": "tool_call_response", "id": "...", "result": "..."} + simple_response: Dict[str, Any] = {"type": "tool_call_response"} + if "id" in tool_output: + simple_response["id"] = tool_output["id"] + if _trace_responses_content and "output" in tool_output: + simple_response["result"] = tool_output["output"] + parts.append(simple_response) + else: + # Original nested format (type "tool_call_output" wraps the tool output) + # Always include type and id, even when content recording is disabled + parts.append({"type": "tool_call_output", "content": tool_output}) + except Exception: # pylint: disable=broad-exception-caught + # Skip items that can't be processed + logger.debug( + "Failed to process tool output item: %s", + output_item, + exc_info=True, + ) + continue + + # Always include parts array with type and id, even when content recording is disabled + content_array = [{"role": "tool", "parts": parts}] if parts else [] + + if _get_use_message_events(): + # Event-based mode: add events + attributes = self._create_event_attributes( + conversation_id=conversation_id, + message_role="tool", + ) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) + + # Use "tool" for the event name: gen_ai.tool.message + span.span_instance.add_event(name=GEN_AI_TOOL_MESSAGE_EVENT, attributes=attributes) + else: + # Attribute-based mode: append to input messages (tool outputs are inputs to the model) + self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) + + def _add_mcp_response_events( + self, + span: "AbstractSpan", + mcp_responses: List[Any], + conversation_id: Optional[str] = None, + ) -> None: + """Add MCP response events (user-provided responses like approval) to the span.""" + parts: List[Dict[str, Any]] = [] + + # Always iterate through mcp_responses to build metadata + if mcp_responses: + for response_item in mcp_responses: + try: + mcp_response: Dict[str, Any] = {} + + # Get the item type - handle both dict and object attributes + if isinstance(response_item, dict): + item_type = response_item.get("type") + else: + item_type = getattr(response_item, "type", None) + + if not item_type: + continue # Skip if no type + + # Use the full MCP type (e.g., "mcp_approval_response") + mcp_response["type"] = item_type + + # Add id/approval_request_id - handle both dict and object + if isinstance(response_item, dict): + response_id = response_item.get("id") or response_item.get("approval_request_id") + else: + response_id = getattr(response_item, "id", None) or getattr( + response_item, "approval_request_id", None + ) + + if response_id: + mcp_response["id"] = response_id + + # Add additional fields only if content recording is enabled + if _trace_responses_content: + # Add approval-specific fields + if isinstance(response_item, dict): + for field in ["approve", "approval_request_id", "status"]: + if field in response_item and response_item[field] is not None: + mcp_response[field] = response_item[field] + else: + for field in ["approve", "approval_request_id", "status"]: + if hasattr(response_item, field): + value = getattr(response_item, field) + if value is not None: + mcp_response[field] = value + + # Add to parts array (type "mcp" wraps the MCP response) + # Always include type and id, even when content recording is disabled + parts.append({"type": "mcp", "content": mcp_response}) + except Exception: # pylint: disable=broad-exception-caught + # Skip items that can't be processed + logger.debug( + "Failed to process MCP response item: %s", + response_item, + exc_info=True, + ) + continue + + # Always include parts array with type and id, even when content recording is disabled + content_array = [{"role": "user", "parts": parts}] if parts else [] + + if _get_use_message_events(): + # Event-based mode: add events + attributes = self._create_event_attributes( + conversation_id=conversation_id, + message_role="user", + ) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) + + # Use user message event name since MCP responses are user inputs + span.span_instance.add_event(name=GEN_AI_USER_MESSAGE_EVENT, attributes=attributes) + else: + # Attribute-based mode: append to input messages (MCP responses are user inputs) + self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) + + def _add_workflow_action_events( + self, + span: "AbstractSpan", + response: Any, + conversation_id: Optional[str] = None, + ) -> None: + """Add workflow action events to the span for workflow agents.""" + if not span or not span.span_instance.is_recording: + return + + # Check if response has output items + if not hasattr(response, "output") or not response.output: + return + + # Iterate through output items looking for workflow_action types + for output_item in response.output: + item_type = getattr(output_item, "type", None) + + if item_type == "workflow_action": + # Extract workflow action attributes + action_id = getattr(output_item, "action_id", None) + status = getattr(output_item, "status", None) + previous_action_id = getattr(output_item, "previous_action_id", None) + workflow_action_id = getattr(output_item, "id", None) + + # Create event attributes + event_attributes = { + GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, + } + + # Build workflow action details object + workflow_details: Dict[str, Any] = {} + + if _trace_responses_content: + # Include action details in content using optimized format + if status: + workflow_details["status"] = status + if action_id: + workflow_details["action_id"] = action_id + if previous_action_id: + workflow_details["previous_action_id"] = previous_action_id + else: + # When content recording is off, only include status + if status: + workflow_details["status"] = status + + # Use consistent format with role and parts wrapper + content_array = [ + { + "role": "workflow", + "parts": [{"type": "workflow_action", "content": workflow_details}], + } + ] + + # Store as JSON array directly without outer wrapper + event_attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) + + # Add the workflow action event + span.span_instance.add_event(name=GEN_AI_WORKFLOW_ACTION_EVENT, attributes=event_attributes) + + # pylint: disable=too-many-branches + def _add_structured_input_events( + self, + span: "AbstractSpan", + input_list: List[Any], + conversation_id: Optional[str] = None, + ) -> None: + """ + Add message events for structured input (list format). + This handles cases like messages with images, multi-part content, etc. + """ + for input_item in input_list: + try: + # Extract role - handle both dict and object + if isinstance(input_item, dict): + role = input_item.get("role", "user") + content = input_item.get("content") + else: + role = getattr(input_item, "role", "user") + content = getattr(input_item, "content", None) + + if not content: + continue + + # Build parts array with content parts + parts: List[Dict[str, Any]] = [] + has_content = False + + # Content can be a list of content items + if isinstance(content, list): + for content_item in content: + content_type = None + has_content = True + + # Handle dict format + if isinstance(content_item, dict): + content_type = content_item.get("type") + if content_type in ("input_text", "text"): + if _trace_responses_content: + text = content_item.get("text") + if text: + parts.append({"type": "text", "content": text}) + else: + parts.append({"type": "text"}) + elif content_type == "input_image": + image_part: Dict[str, Any] = {"type": "image"} + # Include image data if binary data tracing is enabled + if _trace_responses_content and _trace_binary_data: + image_url = content_item.get("image_url") + if image_url: + image_part["content"] = image_url + parts.append(image_part) + elif content_type == "input_file": + file_part: Dict[str, Any] = {"type": "file"} + if _trace_responses_content: + file_content: Dict[str, Any] = {} + # Only include filename and file_id if content recording is enabled + filename = content_item.get("filename") + if filename: + file_content["filename"] = filename + file_id = content_item.get("file_id") + if file_id: + file_content["file_id"] = file_id + # Only include file_data if binary data tracing is enabled + if _trace_binary_data: + file_data = content_item.get("file_data") + if file_data: + file_content["file_data"] = file_data + if file_content: + file_part["content"] = file_content + parts.append(file_part) + elif content_type: + # Other content types (audio, video, etc.) + parts.append({"type": content_type}) + + # Handle object format + elif hasattr(content_item, "type"): + content_type = getattr(content_item, "type", None) + if content_type in ("input_text", "text"): + if _trace_responses_content: + text = getattr(content_item, "text", None) + if text: + parts.append({"type": "text", "content": text}) + else: + parts.append({"type": "text"}) + elif content_type == "input_image": + image_part = {"type": "image"} + # Include image data if binary data tracing is enabled + if _trace_responses_content and _trace_binary_data: + image_url = getattr(content_item, "image_url", None) + if image_url: + image_part["content"] = image_url + parts.append(image_part) + elif content_type == "input_file": + file_part = {"type": "file"} + if _trace_responses_content: + file_content = {} + # Only include filename and file_id if content recording is enabled + filename = getattr(content_item, "filename", None) + if filename: + file_content["filename"] = filename + file_id = getattr(content_item, "file_id", None) + if file_id: + file_content["file_id"] = file_id + # Only include file_data if binary data tracing is enabled + if _trace_binary_data: + file_data = getattr(content_item, "file_data", None) + if file_data: + file_content["file_data"] = file_data + if file_content: + file_part["content"] = file_content + parts.append(file_part) + elif content_type: + # Other content types + parts.append({"type": content_type}) + + # Always create role object and include parts if we detected content + role_obj: Dict[str, Any] = {"role": role} + if parts: + role_obj["parts"] = parts + content_array = [role_obj] + + if _get_use_message_events(): + # Event-based mode + # Create event attributes + attributes = self._create_event_attributes( + conversation_id=conversation_id, + message_role=role, + ) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) + + # Map role to appropriate event name constant + if role == "user": + event_name = GEN_AI_USER_MESSAGE_EVENT + elif role == "assistant": + event_name = GEN_AI_ASSISTANT_MESSAGE_EVENT + else: + # Fallback for any other roles (shouldn't happen in practice) + event_name = f"gen_ai.{role}.message" + + # Add the event + span.span_instance.add_event(name=event_name, attributes=attributes) + else: + # Attribute-based mode + # Append messages to the appropriate attribute + if role in ("user", "tool"): + # User and tool messages go to input.messages + self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) + elif role == "assistant": + # Assistant messages go to output.messages + self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) + + except Exception: # pylint: disable=broad-exception-caught + # Skip items that can't be processed + logger.debug( + "Failed to process structured input item: %s", + input_item, + exc_info=True, + ) + continue + + def _emit_tool_call_event( + self, + span: "AbstractSpan", + tool_call: Dict[str, Any], + conversation_id: Optional[str] = None, + ) -> None: + """Helper to emit a single tool call event or attribute.""" + # Check if simple tool format is enabled for function_call types + if _get_use_simple_tool_format() and tool_call.get("type") == "function_call": + # Build simplified OTEL-compliant format: + # {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} + simple_tool_call: Dict[str, Any] = {"type": "tool_call"} + if "id" in tool_call: + simple_tool_call["id"] = tool_call["id"] + # Extract name and arguments from nested function object if content recording enabled + if _trace_responses_content and "function" in tool_call: + func = tool_call["function"] + if "name" in func: + simple_tool_call["name"] = func["name"] + if "arguments" in func: + simple_tool_call["arguments"] = func["arguments"] + parts = [simple_tool_call] + else: + # Original nested format + parts = [{"type": "tool_call", "content": tool_call}] + + content_array = [{"role": "assistant", "parts": parts}] + + if _get_use_message_events(): + # Original event-based implementation + json_content = json.dumps(content_array, ensure_ascii=False) + attributes = self._create_event_attributes( + conversation_id=conversation_id, + message_role="assistant", + ) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json_content + span.span_instance.add_event(name=GEN_AI_ASSISTANT_MESSAGE_EVENT, attributes=attributes) + else: + # New attribute-based implementation - tool calls are output messages + self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) + + def _emit_tool_output_event( + self, + span: "AbstractSpan", + tool_output: Dict[str, Any], + conversation_id: Optional[str] = None, + ) -> None: + """Helper to emit a single tool output event or attribute.""" + # Check if simple tool format is enabled for function_call_output types + if _get_use_simple_tool_format() and tool_output.get("type") == "function_call_output": + # Build simplified OTEL-compliant format: + # {"type": "tool_call_response", "id": "...", "result": "..."} + simple_tool_response: Dict[str, Any] = {"type": "tool_call_response"} + if "id" in tool_output: + simple_tool_response["id"] = tool_output["id"] + # Add result only if content recording is enabled + if _trace_responses_content and "output" in tool_output: + simple_tool_response["result"] = tool_output["output"] + parts = [simple_tool_response] + else: + # Original nested format + parts = [{"type": "tool_call_output", "content": tool_output}] + + content_array = [{"role": "tool", "parts": parts}] + + if _get_use_message_events(): + # Original event-based implementation + json_content = json.dumps(content_array, ensure_ascii=False) + attributes = self._create_event_attributes( + conversation_id=conversation_id, + message_role="tool", + ) + # Store as JSON array directly without outer wrapper + attributes[GEN_AI_EVENT_CONTENT] = json_content + # Tool outputs are inputs to the model, so use input.messages event + span.span_instance.add_event(name=GEN_AI_USER_MESSAGE_EVENT, attributes=attributes) + else: + # New attribute-based implementation - tool outputs are input messages + self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) + + def _add_tool_call_events( # pylint: disable=too-many-branches + self, + span: "AbstractSpan", + response: Any, + conversation_id: Optional[str] = None, + ) -> None: + """Add tool call events to the span from response output.""" + if not span or not span.span_instance.is_recording: + return + + # Extract function calls and tool calls from response output + output = getattr(response, "output", None) + if not output: + return + + # Process output items for tool call events + for output_item in output: + try: + item_type = getattr(output_item, "type", None) + # Process item based on type + if not item_type: + continue + + tool_call: Dict[str, Any] # Declare once for all branches + + # Handle function_call type + if item_type == "function_call": + tool_call = { + "type": item_type, + } + + # Always include id (needed to correlate with function output) + if hasattr(output_item, "call_id"): + tool_call["id"] = output_item.call_id + + # Only include function name and arguments if content recording is enabled + if _trace_responses_content: + function_details: Dict[str, Any] = {} + if hasattr(output_item, "name"): + function_details["name"] = output_item.name + if hasattr(output_item, "arguments"): + function_details["arguments"] = output_item.arguments + if function_details: + tool_call["function"] = function_details + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle file_search_call type + elif item_type == "file_search_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + + # Only include search details if content recording is enabled + if _trace_responses_content: + # queries and results are directly on the item + if hasattr(output_item, "queries") and output_item.queries: + tool_call["queries"] = output_item.queries + if hasattr(output_item, "results") and output_item.results: + tool_call["results"] = [] + for result in output_item.results: + result_data = { + "file_id": getattr(result, "file_id", None), + "filename": getattr(result, "filename", None), + "score": getattr(result, "score", None), + } + tool_call["results"].append(result_data) + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle code_interpreter_call type + elif item_type == "code_interpreter_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + + # Only include code interpreter details if content recording is enabled + if _trace_responses_content: + # code and outputs are directly on the item + if hasattr(output_item, "code") and output_item.code: + tool_call["code"] = output_item.code + if hasattr(output_item, "outputs") and output_item.outputs: + tool_call["outputs"] = [] + for output in output_item.outputs: + # Outputs can be logs or images + output_data = { + "type": getattr(output, "type", None), + } + if hasattr(output, "logs"): + output_data["logs"] = output.logs + elif hasattr(output, "image"): + output_data["image"] = {"file_id": getattr(output.image, "file_id", None)} + tool_call["outputs"].append(output_data) + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle web_search_call type + elif item_type == "web_search_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + + # Only include search action if content recording is enabled + if _trace_responses_content: + # action is directly on the item + if hasattr(output_item, "action") and output_item.action: + # WebSearchAction has type and type-specific fields + tool_call["action"] = { + "type": getattr(output_item.action, "type", None), + } + # Try to capture action-specific fields + if hasattr(output_item.action, "query"): + tool_call["action"]["query"] = output_item.action.query + if hasattr(output_item.action, "results"): + tool_call["action"]["results"] = [] + for result in output_item.action.results: + result_data = { + "title": getattr(result, "title", None), + "url": getattr(result, "url", None), + } + tool_call["action"]["results"].append(result_data) + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle azure_ai_search_call type + elif item_type == "azure_ai_search_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + elif hasattr(output_item, "call_id"): + tool_call["id"] = output_item.call_id + + # Only include search details if content recording is enabled + if _trace_responses_content: + # Add Azure AI Search specific fields + if hasattr(output_item, "input") and output_item.input: + tool_call["input"] = output_item.input + + if hasattr(output_item, "results") and output_item.results: + tool_call["results"] = [] + for result in output_item.results: + result_data = {} + if hasattr(result, "title"): + result_data["title"] = result.title + if hasattr(result, "url"): + result_data["url"] = result.url + if hasattr(result, "content"): + result_data["content"] = result.content + if result_data: + tool_call["results"].append(result_data) + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle image_generation_call type + elif item_type == "image_generation_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + elif hasattr(output_item, "call_id"): + tool_call["id"] = output_item.call_id + + # Only include image generation details if content recording is enabled + if _trace_responses_content: + # Include metadata fields + if hasattr(output_item, "prompt"): + tool_call["prompt"] = output_item.prompt + if hasattr(output_item, "quality"): + tool_call["quality"] = output_item.quality + if hasattr(output_item, "size"): + tool_call["size"] = output_item.size + if hasattr(output_item, "style"): + tool_call["style"] = output_item.style + + # Include the result (image data) only if binary data tracing is enabled + if _trace_binary_data and hasattr(output_item, "result") and output_item.result: + tool_call["result"] = output_item.result + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle mcp_call type (Model Context Protocol) + elif item_type == "mcp_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + + # Only include MCP details if content recording is enabled + if _trace_responses_content: + if hasattr(output_item, "name"): + tool_call["name"] = output_item.name + if hasattr(output_item, "arguments"): + tool_call["arguments"] = output_item.arguments + if hasattr(output_item, "server_label"): + tool_call["server_label"] = output_item.server_label + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle other MCP types (mcp_list_tools, mcp_approval_request, etc.) + elif item_type and item_type.startswith("mcp_"): + tool_call = { + "type": item_type, # Preserve the specific MCP type + } + + # Always include ID if available + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + elif hasattr(output_item, "call_id"): + tool_call["id"] = output_item.call_id + + # Only include additional details if content recording is enabled + if _trace_responses_content: + # Try to capture common MCP fields + for field in [ + "name", + "server_label", + "arguments", + "approval_request_id", + "approve", + "status", + ]: + if hasattr(output_item, field): + value = getattr(output_item, field) + if value is not None: + tool_call[field] = value + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle computer_call type (for computer use) + elif item_type == "computer_call": + tool_call = { + "type": item_type, + } + + if hasattr(output_item, "call_id"): + tool_call["call_id"] = output_item.call_id + + # Only include computer action details if content recording is enabled + if _trace_responses_content: + # action is directly on the item + if hasattr(output_item, "action") and output_item.action: + # ComputerAction has type and type-specific fields + tool_call["action"] = { + "type": getattr(output_item.action, "type", None), + } + # Try to capture common action fields + for attr in ["x", "y", "text", "key", "command", "scroll"]: + if hasattr(output_item.action, attr): + tool_call["action"][attr] = getattr(output_item.action, attr) + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle remote_function_call_output type (remote tool calls like Azure AI Search) + elif item_type == "remote_function_call_output": + # Extract the tool name from the output item + tool_name = getattr(output_item, "name", None) if hasattr(output_item, "name") else None + + tool_call = { + "type": tool_name if tool_name else "remote_function", + } + + # Always include ID (needed for correlation) + if hasattr(output_item, "id"): + tool_call["id"] = output_item.id + elif hasattr(output_item, "call_id"): + tool_call["id"] = output_item.call_id + # Check model_extra for call_id + elif hasattr(output_item, "model_extra") and isinstance(output_item.model_extra, dict): + if "call_id" in output_item.model_extra: + tool_call["id"] = output_item.model_extra["call_id"] + + # Only include tool details if content recording is enabled + if _trace_responses_content: + # Extract data from model_extra if available (Pydantic v2 style) + if hasattr(output_item, "model_extra") and isinstance(output_item.model_extra, dict): + for key, value in output_item.model_extra.items(): + # Skip already captured fields, redundant fields (name, label), and empty/None values + if ( + key not in ["type", "id", "call_id", "name", "label"] + and value is not None + and value != "" + ): + tool_call[key] = value + + # Also try as_dict if available + if hasattr(output_item, "as_dict"): + try: + tool_dict = output_item.as_dict() + # Extract relevant fields (exclude already captured ones and empty/None values) + for key, value in tool_dict.items(): + if key not in [ + "type", + "id", + "call_id", + "name", + "label", + "role", + "content", + ]: + # Skip empty strings and None values + if value is not None and value != "": + # Don't overwrite if already exists + if key not in tool_call: + tool_call[key] = value + except Exception as e: + logger.debug(f"Failed to extract data from as_dict: {e}") + + # Fallback: try common fields directly (skip if empty and skip redundant name/label) + for field in [ + "input", + "output", + "results", + "status", + "error", + "search_query", + "query", + ]: + if hasattr(output_item, field): + try: + value = getattr(output_item, field) + if value is not None and value != "": + # If not already in tool_call, add it + if field not in tool_call: + tool_call[field] = value + except Exception: + pass + + self._emit_tool_call_event(span, tool_call, conversation_id) + + # Handle unknown/future tool call types with best effort + # Exclude _output types - those are handled separately as tool outputs, not tool calls + elif item_type and "_call" in item_type and not item_type.endswith("_output"): + # Generic handler for tool calls + try: + tool_call = { + "type": item_type, + } + + # Always try to include common ID fields (safe, needed for correlation) + for id_field in ["id", "call_id"]: + if hasattr(output_item, id_field): + tool_call["id" if id_field == "id" else "id"] = getattr(output_item, id_field) + break # Use first available ID field + + # Only include detailed fields if content recording is enabled + if _trace_responses_content: + # Try to get the full tool details using model_dump() for Pydantic models + if hasattr(output_item, "model_dump"): + tool_dict = output_item.model_dump() + # Extract the tool-specific details (exclude common fields already captured) + for key, value in tool_dict.items(): + if ( + key + not in ["type", "id", "call_id", "role", "content", "status", "partition_key"] + and value is not None + ): + tool_call[key] = value + elif hasattr(output_item, "as_dict"): + tool_dict = output_item.as_dict() + # Extract the tool-specific details (exclude common fields already captured) + for key, value in tool_dict.items(): + if key not in ["type", "id", "call_id"] and value is not None: + tool_call[key] = value + else: + # Fallback: try to capture common fields manually + for field in [ + "name", + "arguments", + "input", + "query", + "search_query", + "server_label", + ]: + if hasattr(output_item, field): + value = getattr(output_item, field) + if value is not None: + tool_call[field] = value + + self._emit_tool_call_event(span, tool_call, conversation_id) + + except Exception as e: + # Log but don't crash if we can't handle an unknown tool type + logger.debug(f"Failed to process unknown tool call type '{item_type}': {e}") + + # Handle unknown/future tool output types with best effort + # These are the _output types that correspond to the tool calls above + elif item_type and item_type.endswith("_output"): + # Generic handler for tool outputs + try: + tool_output = { + "type": item_type, + } + + # Always try to include common ID fields (safe, needed for correlation) + for id_field in ["id", "call_id"]: + if hasattr(output_item, id_field): + tool_output["id"] = getattr(output_item, id_field) + break # Use first available ID field + + # Only include detailed fields if content recording is enabled + if _trace_responses_content: + # Try to get the full tool output using model_dump() for Pydantic models + if hasattr(output_item, "model_dump"): + output_dict = output_item.model_dump() + # Extract the tool-specific output (exclude common fields already captured) + # Include fields even if empty string (but not None) for API consistency + for key, value in output_dict.items(): + if ( + key + not in ["type", "id", "call_id", "role", "content", "status", "partition_key"] + and value is not None + ): + tool_output[key] = value + elif hasattr(output_item, "as_dict"): + output_dict = output_item.as_dict() + # Extract the tool-specific output (exclude common fields already captured) + for key, value in output_dict.items(): + if ( + key not in ["type", "id", "call_id", "role", "content", "status"] + and value is not None + ): + tool_output[key] = value + else: + # Fallback: try to capture common output fields manually + for field in [ + "output", + "result", + "results", + "data", + "response", + ]: + if hasattr(output_item, field): + value = getattr(output_item, field) + if value is not None: + tool_output[field] = value + + self._emit_tool_output_event(span, tool_output, conversation_id) + + except Exception as e: + # Log but don't crash if we can't handle an unknown tool output type + logger.debug(f"Failed to process unknown tool output type '{item_type}': {e}") + + except Exception as e: + # Catch-all to prevent any tool call processing errors from breaking instrumentation + logger.debug(f"Error processing tool call events: {e}") + + def start_responses_span( + self, + server_address: Optional[str] = None, + port: Optional[int] = None, + model: Optional[str] = None, + assistant_name: Optional[str] = None, + agent_id: Optional[str] = None, + conversation_id: Optional[str] = None, + input_text: Optional[str] = None, + input_raw: Optional[Any] = None, + stream: bool = False, # pylint: disable=unused-argument + tools: Optional[List[Dict[str, Any]]] = None, + ) -> "Optional[AbstractSpan]": + """Start a span for responses API call.""" + # Build span name: agent case uses "invoke_agent", non-agent case uses "chat" + if assistant_name: + span_name = f"{SPAN_NAME_INVOKE_AGENT} {assistant_name}" + operation_name_value = OPERATION_NAME_INVOKE_AGENT + elif model: + span_name = f"{SPAN_NAME_CHAT} {model}" + operation_name_value = OPERATION_NAME_CHAT + else: + span_name = OperationName.RESPONSES.value + operation_name_value = OperationName.RESPONSES.value + + span = start_span( + operation_name=OperationName.RESPONSES, + server_address=server_address, + port=port, + span_name=span_name, + model=model, + gen_ai_provider=RESPONSES_PROVIDER, + ) + + if span and span.span_instance.is_recording: + # Set operation name attribute (start_span doesn't set this automatically) + self._set_attributes( + span, + (GEN_AI_OPERATION_NAME, operation_name_value), + ) + + # Set response-specific attributes that start_span doesn't handle + # Note: model and server_address are already set by start_span, so we don't need to set them again + self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) + self._set_span_attribute_safe(span, GEN_AI_AGENT_NAME, assistant_name) + self._set_span_attribute_safe(span, GEN_AI_AGENT_ID, agent_id) + + # Set tools attribute if tools are provided + if tools: + # Convert tools list to JSON string for the attribute + tools_json = json.dumps(tools, ensure_ascii=False) + self._set_span_attribute_safe(span, GEN_AI_REQUEST_TOOLS, tools_json) + + # Process input - check if it contains tool outputs or MCP responses + tool_outputs = [] + mcp_responses = [] + has_tool_outputs = False + has_mcp_responses = False + + # Use input_raw (or input_text if it's a list) to check for tool outputs + input_to_check = input_raw if input_raw is not None else input_text + + # Check if input is a list (structured input with potential tool outputs) + if isinstance(input_to_check, list): + for item in input_to_check: + # Check if this item has type "function_call_output" or similar + item_type = None + if hasattr(item, "type"): + item_type = getattr(item, "type", None) + elif isinstance(item, dict): + item_type = item.get("type") + + if item_type and ("output" in item_type or item_type == "function_call_output"): + has_tool_outputs = True + tool_outputs.append(item) + elif item_type and item_type.startswith("mcp_") and "response" in item_type: + # MCP responses (mcp_approval_response, etc.) are user inputs + has_mcp_responses = True + mcp_responses.append(item) + + # Add appropriate message events based on input type + if has_tool_outputs: + # Add tool message event for tool outputs + self._add_tool_message_events( + span, + tool_outputs=tool_outputs, + conversation_id=conversation_id, + ) + elif has_mcp_responses: + # Add MCP response events (user providing approval/response) + self._add_mcp_response_events( + span, + mcp_responses=mcp_responses, + conversation_id=conversation_id, + ) + elif input_text and not isinstance(input_text, list): + # Add regular user message event (only if input_text is a string, not a list) + self._add_message_event( + span, + role="user", + content=input_text, + conversation_id=conversation_id, + ) + elif isinstance(input_to_check, list) and not has_tool_outputs: + # Handle structured input (list format) - extract text content from user messages + # This handles cases like image inputs with text prompts + self._add_structured_input_events( + span, + input_list=input_to_check, + conversation_id=conversation_id, + ) + + return span + + def _extract_server_info_from_client( + self, client: Any + ) -> Tuple[Optional[str], Optional[int]]: # pylint: disable=docstring-missing-return,docstring-missing-rtype + """Extract server address and port from OpenAI client.""" + try: + # First try direct access to base_url + if hasattr(client, "base_url") and client.base_url: + return self._parse_url(str(client.base_url)) + if hasattr(client, "_base_url") and client._base_url: # pylint: disable=protected-access + return self._parse_url(str(client._base_url)) + + # Try the nested client structure as suggested + base_client = getattr(client, "_client", None) + if base_client: + base_url = getattr(base_client, "base_url", None) + if base_url: + return self._parse_url(str(base_url)) + except Exception: # pylint: disable=broad-exception-caught + pass + return None, None + + def _extract_conversation_id(self, kwargs: Dict[str, Any]) -> Optional[str]: + """Extract conversation ID from kwargs.""" + return kwargs.get("conversation") or kwargs.get("conversation_id") + + def _extract_model(self, kwargs: Dict[str, Any]) -> Optional[str]: + """Extract model from kwargs.""" + return kwargs.get("model") + + def _extract_assistant_name(self, kwargs: Dict[str, Any]) -> Optional[str]: + """Extract assistant/agent name from kwargs.""" + extra_body = kwargs.get("extra_body") + if extra_body and isinstance(extra_body, dict): + agent_info = extra_body.get("agent_reference") + if agent_info and isinstance(agent_info, dict): + return agent_info.get("name") + return None + + def _extract_agent_id(self, kwargs: Dict[str, Any]) -> Optional[str]: + """Extract agent ID from kwargs.""" + extra_body = kwargs.get("extra_body") + if extra_body and isinstance(extra_body, dict): + agent_info = extra_body.get("agent_reference") + if agent_info and isinstance(agent_info, dict): + return agent_info.get("id") + return None + + def _extract_input_text(self, kwargs: Dict[str, Any]) -> Optional[str]: + """Extract input text from kwargs.""" + return kwargs.get("input") + + def _extract_finish_reason(self, response: Any) -> Optional[str]: + """Extract finish reason from response output.""" + if hasattr(response, "output") and response.output: + try: + # Check if output is a list (typical case) + if isinstance(response.output, list) and len(response.output) > 0: + output_item = response.output[0] # Get first output item + + # Try finish_reason field + if hasattr(output_item, "finish_reason") and output_item.finish_reason: + return output_item.finish_reason + + # Try finish_details.type (Azure AI Agents structure) + if hasattr(output_item, "finish_details") and output_item.finish_details: + if hasattr(output_item.finish_details, "type"): + return output_item.finish_details.type + except (AttributeError, TypeError, IndexError): + pass + + # Fallback: check response.status directly + if hasattr(response, "status"): + return response.status + + return None + + def _extract_output_text(self, response: Any) -> Optional[str]: + """Extract output text from response.""" + if hasattr(response, "output") and response.output: + # Handle simple string output (for tests/simple cases) + if isinstance(response.output, str): + return response.output + + # Handle complex output structure (list of response messages) + output_texts = [] + try: + for output_item in response.output: + if hasattr(output_item, "content") and output_item.content: + # content is typically a list of content blocks + for content_block in output_item.content: + if hasattr(content_block, "text"): + output_texts.append(content_block.text) + elif hasattr(content_block, "output_text") and hasattr(content_block.output_text, "text"): + # Handle ResponseOutputText structure + output_texts.append(content_block.output_text.text) + elif isinstance(content_block, str): + output_texts.append(content_block) + elif isinstance(output_item, str): + # Handle simple string items + output_texts.append(output_item) + + if output_texts: + return " ".join(output_texts) + except (AttributeError, TypeError): + # Fallback: convert to string but log for debugging + logger.debug( + "Failed to extract structured output text, falling back to string conversion: %s", + response.output, + ) + return str(response.output) + return None + + def _extract_responses_api_attributes(self, span: "AbstractSpan", response: Any) -> None: + """Extract and set attributes for Responses API responses.""" + try: + # Extract and set response model + model = getattr(response, "model", None) + self._set_span_attribute_safe(span, GEN_AI_RESPONSE_MODEL, model) + + # Extract and set response ID + response_id = getattr(response, "id", None) + self._set_span_attribute_safe(span, GEN_AI_RESPONSE_ID, response_id) + + # Extract and set system fingerprint if available + system_fingerprint = getattr(response, "system_fingerprint", None) + self._set_span_attribute_safe(span, GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT, system_fingerprint) + + # Extract and set usage information (Responses API may use input_tokens/output_tokens) + usage = getattr(response, "usage", None) + if usage: + # Try input_tokens first, then prompt_tokens for compatibility + input_tokens = getattr(usage, "input_tokens", None) or getattr(usage, "prompt_tokens", None) + # Try output_tokens first, then completion_tokens for compatibility + output_tokens = getattr(usage, "output_tokens", None) or getattr(usage, "completion_tokens", None) + # total_tokens = getattr(usage, "total_tokens", None) # Unused + + self._set_span_attribute_safe(span, GEN_AI_USAGE_INPUT_TOKENS, input_tokens) + self._set_span_attribute_safe(span, GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens) + # self._set_span_attribute_safe(span, GEN_AI_USAGE_TOTAL_TOKENS, total_tokens) # Commented out as redundant + + # Extract finish reasons from output items (Responses API structure) + output = getattr(response, "output", None) + if output: + finish_reasons = [] + for item in output: + if hasattr(item, "finish_reason") and item.finish_reason: + finish_reasons.append(item.finish_reason) + + if finish_reasons: + self._set_span_attribute_safe(span, GEN_AI_RESPONSE_FINISH_REASONS, finish_reasons) + else: + # Handle single finish reason (not in output array) + finish_reason = getattr(response, "finish_reason", None) + if finish_reason: + self._set_span_attribute_safe(span, GEN_AI_RESPONSE_FINISH_REASONS, [finish_reason]) + + except Exception as e: + logger.debug(f"Error extracting responses API attributes: {e}") + + def _extract_conversation_attributes(self, span: "AbstractSpan", response: Any) -> None: + """Extract and set attributes for conversation creation responses.""" + try: + # Extract and set conversation ID + conversation_id = getattr(response, "id", None) + self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) + + # Set response object type + # self._set_span_attribute_safe(span, GEN_AI_RESPONSE_OBJECT, "conversation") + + except Exception as e: + logger.debug(f"Error extracting conversation attributes: {e}") + + def _extract_conversation_items_attributes( + self, span: "AbstractSpan", response: Any, args: Tuple, kwargs: Dict[str, Any] + ) -> None: + """Extract and set attributes for conversation items list responses.""" + try: + # Set response object type for list operations + # self._set_span_attribute_safe(span, GEN_AI_RESPONSE_OBJECT, "list") + + # Extract conversation_id from request parameters + conversation_id = None + if args and len(args) > 1: + # Second argument might be conversation_id + conversation_id = args[1] + elif "conversation_id" in kwargs: + conversation_id = kwargs["conversation_id"] + + if conversation_id: + self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) + + # Note: Removed gen_ai.response.has_more attribute as requested + + except Exception as e: + logger.debug(f"Error extracting conversation items attributes: {e}") + + def _extract_response_attributes(self, response: Any) -> Dict[str, Any]: + """Extract response attributes from response object (legacy method for backward compatibility).""" + attributes = {} + + try: + # Extract response model + model = getattr(response, "model", None) + if model: + attributes[GEN_AI_RESPONSE_MODEL] = model + + # Extract response ID + response_id = getattr(response, "id", None) + if response_id: + attributes[GEN_AI_RESPONSE_ID] = response_id + + # Extract usage information + usage = getattr(response, "usage", None) + if usage: + prompt_tokens = getattr(usage, "prompt_tokens", None) + completion_tokens = getattr(usage, "completion_tokens", None) + # total_tokens = getattr(usage, "total_tokens", None) # Unused + + if prompt_tokens: + attributes[GEN_AI_USAGE_INPUT_TOKENS] = prompt_tokens + if completion_tokens: + attributes[GEN_AI_USAGE_OUTPUT_TOKENS] = completion_tokens + # if total_tokens: + # attributes[GEN_AI_USAGE_TOTAL_TOKENS] = total_tokens # Commented out as redundant + + # Extract finish reasons from output items (Responses API structure) + output = getattr(response, "output", None) + if output: + finish_reasons = [] + for item in output: + if hasattr(item, "finish_reason") and item.finish_reason: + finish_reasons.append(item.finish_reason) + + if finish_reasons: + attributes[GEN_AI_RESPONSE_FINISH_REASONS] = finish_reasons + else: + finish_reason = getattr(response, "finish_reason", None) + if finish_reason: + attributes[GEN_AI_RESPONSE_FINISH_REASONS] = [finish_reason] + + except Exception as e: + logger.debug(f"Error extracting response attributes: {e}") + + return attributes + + def _create_responses_span_from_parameters(self, *args, **kwargs): + """Extract parameters and create span for responses API tracing.""" + # Extract client from args (first argument) + client = args[0] if args else None + server_address, port = self._extract_server_info_from_client(client) + + # Extract parameters from kwargs + conversation_id = self._extract_conversation_id(kwargs) + model = self._extract_model(kwargs) + assistant_name = self._extract_assistant_name(kwargs) + agent_id = self._extract_agent_id(kwargs) + input_text = self._extract_input_text(kwargs) + input_raw = kwargs.get("input") # Get the raw input (could be string or list) + stream = kwargs.get("stream", False) + + # Create and return the span + return self.start_responses_span( + server_address=server_address, + port=port, + model=model, + assistant_name=assistant_name, + agent_id=agent_id, + conversation_id=conversation_id, + input_text=input_text, + input_raw=input_raw, + stream=stream, + ) + + def trace_responses_create(self, function, *args, **kwargs): + """Trace synchronous responses.create calls.""" + # If stream=True and we're being called from responses.stream(), skip tracing + # The responses.stream() method internally calls create(stream=True), and + # trace_responses_stream() will handle the tracing for that case. + # We only trace direct calls to create(stream=True) from user code. + if kwargs.get("stream", False): + # Check if we're already in a stream tracing context + # by looking at the call stack + import inspect + + frame = inspect.currentframe() + if frame and frame.f_back and frame.f_back.f_back: + # Check if the caller is trace_responses_stream + caller_name = frame.f_back.f_back.f_code.co_name + if caller_name in ( + "trace_responses_stream", + "trace_responses_stream_async", + "__enter__", + "__aenter__", + ): + # We're being called from responses.stream(), don't create a new span + return function(*args, **kwargs) + + span = self._create_responses_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + model = self._extract_model(kwargs) + operation_name = "responses" + + start_time = time.time() + + if span is None: + # Still record metrics even without spans + try: + result = function(*args, **kwargs) + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + raise + + # Handle streaming vs non-streaming responses differently + stream = kwargs.get("stream", False) + if stream: + # For streaming, don't use context manager - let wrapper handle span lifecycle + try: + result = function(*args, **kwargs) + result = self._wrap_streaming_response( + result, + span, + kwargs, + start_time, + operation_name, + server_address, + port, + model, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + self.record_error(span, e) + span.span_instance.end() + raise + else: + # For non-streaming, use context manager as before + with span: + try: + result = function(*args, **kwargs) + duration = time.time() - start_time + + # Extract and set response attributes + self._extract_responses_api_attributes(span, result) + + # Add tool call events (if any) + conversation_id = self._extract_conversation_id(kwargs) + self._add_tool_call_events(span, result, conversation_id) + + # Add workflow action events (if any) + self._add_workflow_action_events(span, result, conversation_id) + + # Add assistant message event + output_text = self._extract_output_text(result) + if output_text: + finish_reason = self._extract_finish_reason(result) + self._add_message_event( + span, + role="assistant", + content=output_text, + conversation_id=conversation_id, + finish_reason=finish_reason, + ) + + # Record metrics using new dedicated method + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + # pyright: ignore [reportPossiblyUnboundVariable] + span.span_instance.set_status(StatusCode.OK) + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + span.span_instance.record_exception(e) + raise + return result + + async def trace_responses_create_async(self, function, *args, **kwargs): + """Trace asynchronous responses.create calls.""" + # If stream=True and we're being called from responses.stream(), skip tracing + # The responses.stream() method internally calls create(stream=True), and + # trace_responses_stream() will handle the tracing for that case. + # We only trace direct calls to create(stream=True) from user code. + if kwargs.get("stream", False): + # Check if we're already in a stream tracing context + # by looking at the call stack + import inspect + + frame = inspect.currentframe() + if frame and frame.f_back and frame.f_back.f_back: + # Check if the caller is trace_responses_stream + caller_name = frame.f_back.f_back.f_code.co_name + if caller_name in ( + "trace_responses_stream", + "trace_responses_stream_async", + "__enter__", + "__aenter__", + ): + # We're being called from responses.stream(), don't create a new span + return await function(*args, **kwargs) + + span = self._create_responses_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + model = self._extract_model(kwargs) + operation_name = "responses" + + start_time = time.time() + + if span is None: + # Still record metrics even without spans + try: + result = await function(*args, **kwargs) + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + raise + + # Handle streaming vs non-streaming responses differently + stream = kwargs.get("stream", False) + if stream: + # For streaming, don't use context manager - let wrapper handle span lifecycle + try: + result = await function(*args, **kwargs) + result = self._wrap_async_streaming_response( + result, + span, + kwargs, + start_time, + operation_name, + server_address, + port, + model, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + self.record_error(span, e) + span.span_instance.end() + raise + else: + # For non-streaming, use context manager as before + with span: + try: + result = await function(*args, **kwargs) + duration = time.time() - start_time + + # Extract and set response attributes + self._extract_responses_api_attributes(span, result) + + # Add tool call events (if any) + conversation_id = self._extract_conversation_id(kwargs) + self._add_tool_call_events(span, result, conversation_id) + + # Add workflow action events (if any) + self._add_workflow_action_events(span, result, conversation_id) + + # Add assistant message event + output_text = self._extract_output_text(result) + if output_text: + finish_reason = self._extract_finish_reason(result) + self._add_message_event( + span, + role="assistant", + content=output_text, + conversation_id=conversation_id, + finish_reason=finish_reason, + ) + + # Record metrics using new dedicated method + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + # pyright: ignore [reportPossiblyUnboundVariable] + span.span_instance.set_status(StatusCode.OK) + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + span.span_instance.record_exception(e) + raise + return result + + def trace_responses_stream(self, function, *args, **kwargs): + """Trace synchronous responses.stream calls.""" + span = self._create_responses_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + model = self._extract_model(kwargs) + operation_name = "responses" + + start_time = time.time() + + if span is None: + # No tracing, just call the function + return function(*args, **kwargs) + + # For responses.stream(), always wrap the ResponseStreamManager + try: + result = function(*args, **kwargs) + # Detect if it's async or sync stream manager by checking for __aenter__ + if hasattr(result, "__aenter__"): + # Async stream manager + result = self._wrap_async_response_stream_manager( + result, + span, + kwargs, + start_time, + operation_name, + server_address, + port, + model, + ) + else: + # Sync stream manager + result = self._wrap_response_stream_manager( + result, + span, + kwargs, + start_time, + operation_name, + server_address, + port, + model, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + self.record_error(span, e) + span.span_instance.end() + raise + + def trace_responses_stream_async(self, function, *args, **kwargs): + """Trace asynchronous responses.stream calls.""" + span = self._create_responses_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + model = self._extract_model(kwargs) + operation_name = "responses" + + start_time = time.time() + + if span is None: + # No tracing, just call the function (don't await - it returns async context manager) + return function(*args, **kwargs) + + # For responses.stream(), always wrap the AsyncResponseStreamManager + # Note: stream() itself is not async, it returns an AsyncResponseStreamManager synchronously + try: + result = function(*args, **kwargs) + # Wrap the AsyncResponseStreamManager + result = self._wrap_async_response_stream_manager( + result, + span, + kwargs, + start_time, + operation_name, + server_address, + port, + model, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: model, + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + self.record_error(span, e) + span.span_instance.end() + raise + + def _wrap_streaming_response( + self, + stream, + span: "AbstractSpan", + original_kwargs: Dict[str, Any], + start_time: float, + operation_name: str, + server_address: Optional[str], + port: Optional[int], + model: Optional[str], + ): + """Wrap a streaming response to trace chunks.""" + conversation_id = self._extract_conversation_id(original_kwargs) + instrumentor = self # Capture the instrumentor instance + + class StreamWrapper: # pylint: disable=too-many-instance-attributes,protected-access + def __init__( + self, + stream_iter, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ): + self.stream_iter = stream_iter + self.span = span + self.conversation_id = conversation_id + self.instrumentor = instrumentor + self.accumulated_content = [] + self.span_ended = False + self.start_time = start_time + self.operation_name = operation_name + self.server_address = server_address + self.port = port + self.model = model + + # Enhanced properties for sophisticated chunk processing + self.accumulated_output = [] + self.response_id = None + self.response_model = None + self.service_tier = None + self.input_tokens = 0 + self.output_tokens = 0 + self.finish_reason = None # Track finish_reason from streaming chunks + + # Track all output items from streaming events (tool calls, workflow actions, etc.) + # Use (id, type) as key to avoid overwriting when call and output have same ID + self.output_items = {} # Dict[(item_id, item_type), output_item] + self.has_output_items = False + + # Expose response attribute for compatibility with ResponseStreamManager + self.response = getattr(stream_iter, "response", None) or getattr(stream_iter, "_response", None) + + def append_output_content(self, content): + """Append content to accumulated output list.""" + if content: + self.accumulated_output.append(str(content)) + + def set_response_metadata(self, chunk): + """Update response metadata from chunk if not already set.""" + chunk_type = getattr(chunk, "type", None) + + if not self.response_id: + self.response_id = getattr(chunk, "id", None) + if not self.response_model: + self.response_model = getattr(chunk, "model", None) + if not self.service_tier: + self.service_tier = getattr(chunk, "service_tier", None) + + # Extract finish_reason from response.output_item.done events + if chunk_type == "response.output_item.done" and hasattr(chunk, "item"): + item = chunk.item + if hasattr(item, "status") and item.status: + self.finish_reason = item.status + # Also check for direct finish_reason attribute + elif hasattr(chunk, "finish_reason") and chunk.finish_reason: + self.finish_reason = chunk.finish_reason + # Also check for finish_details in output items (Azure AI Agents structure) + elif hasattr(chunk, "output") and chunk.output: + if isinstance(chunk.output, list) and len(chunk.output) > 0: + output_item = chunk.output[0] + if hasattr(output_item, "finish_details") and output_item.finish_details: + if hasattr(output_item.finish_details, "type"): + self.finish_reason = output_item.finish_details.type + + def process_chunk(self, chunk): + """Process chunk to accumulate data and update metadata.""" + # Check for output item events in streaming + chunk_type = getattr(chunk, "type", None) + + # Collect all complete output items from ResponseOutputItemDoneEvent or ResponseOutputItemAddedEvent + # This includes function_call, file_search_tool_call, code_interpreter_tool_call, + # web_search, mcp_call, computer_tool_call, custom_tool_call, workflow_action, and any future types + if (chunk_type in ("response.output_item.done", "response.output_item.added")) and hasattr( + chunk, "item" + ): + item = chunk.item + item_type = getattr(item, "type", None) + + # Collect any output item (tool calls, workflow actions, etc.) + if item_type: + # Use call_id, action_id, or id as the key (workflow actions use action_id) + item_id = ( + getattr(item, "call_id", None) + or getattr(item, "action_id", None) + or getattr(item, "id", None) + ) + if item_id: + # Use (id, type) tuple as key to distinguish call from output + key = (item_id, item_type) + self.output_items[key] = item + self.has_output_items = True + # Items without ID or type are skipped + + # Capture response ID from ResponseCreatedEvent or ResponseCompletedEvent + if chunk_type == "response.created" and hasattr(chunk, "response"): + if not self.response_id: + self.response_id = chunk.response.id + self.response_model = getattr(chunk.response, "model", None) + elif chunk_type == "response.completed" and hasattr(chunk, "response"): + if not self.response_id: + self.response_id = chunk.response.id + if not self.response_model: + self.response_model = getattr(chunk.response, "model", None) + # Extract usage from the completed response + if hasattr(chunk.response, "usage"): + response_usage = chunk.response.usage + if hasattr(response_usage, "input_tokens") and response_usage.input_tokens: + self.input_tokens = response_usage.input_tokens + if hasattr(response_usage, "output_tokens") and response_usage.output_tokens: + self.output_tokens = response_usage.output_tokens + # Also handle standard token field names for compatibility + if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens: + self.input_tokens = response_usage.prompt_tokens + if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens: + self.output_tokens = response_usage.completion_tokens + + # Only append TEXT content from delta events (not function call arguments or other deltas) + # Text deltas can come as: + # 1. response.text.delta - has delta as string + # 2. response.output_item.delta - has delta.text attribute + # Function call arguments come via response.function_call_arguments.delta - has delta as JSON string + # We need to avoid appending function call arguments + if chunk_type and ".delta" in chunk_type and hasattr(chunk, "delta"): + # If it's function_call_arguments.delta, skip it + if "function_call_arguments" not in chunk_type: + # Check if delta is a string (text content) or has .text attribute + if isinstance(chunk.delta, str): + self.append_output_content(chunk.delta) + elif hasattr(chunk.delta, "text"): + self.append_output_content(chunk.delta.text) + + # Always update metadata + self.set_response_metadata(chunk) + + # Handle usage info + usage = getattr(chunk, "usage", None) + if usage: + if hasattr(usage, "input_tokens") and usage.input_tokens: + self.input_tokens += usage.input_tokens + if hasattr(usage, "output_tokens") and usage.output_tokens: + self.output_tokens += usage.output_tokens + # Also handle standard token field names + if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: + self.input_tokens += usage.prompt_tokens + if hasattr(usage, "completion_tokens") and usage.completion_tokens: + self.output_tokens += usage.completion_tokens + + def cleanup(self): + """Perform final cleanup when streaming is complete.""" + if not self.span_ended: + duration = time.time() - self.start_time + + # Join all accumulated output content + complete_content = "".join(self.accumulated_output) + + if self.span.span_instance.is_recording: + # Add tool call events if we detected any output items (tool calls, etc.) + if self.has_output_items: + # Create mock response with output items for event generation + # The existing _add_tool_call_events method handles all tool types + mock_response = type( + "Response", + (), + {"output": list(self.output_items.values())}, + )() + self.instrumentor._add_tool_call_events( + self.span, + mock_response, + self.conversation_id, + ) + # Also add workflow action events + self.instrumentor._add_workflow_action_events( + self.span, + mock_response, + self.conversation_id, + ) + + # Only add assistant message event if there's actual text content (not empty/whitespace) + if complete_content and complete_content.strip(): + self.instrumentor._add_message_event( + self.span, + role="assistant", + content=complete_content, + conversation_id=self.conversation_id, + finish_reason=self.finish_reason, + ) + + # Set final span attributes using accumulated metadata + if self.response_id: + self.instrumentor._set_span_attribute_safe(self.span, GEN_AI_RESPONSE_ID, self.response_id) + if self.response_model: + self.instrumentor._set_span_attribute_safe( + self.span, GEN_AI_RESPONSE_MODEL, self.response_model + ) + + if self.service_tier: + self.instrumentor._set_span_attribute_safe( + self.span, + GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, + self.service_tier, + ) + + # Set token usage span attributes + if self.input_tokens > 0: + self.instrumentor._set_span_attribute_safe( + self.span, GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens + ) + if self.output_tokens > 0: + self.instrumentor._set_span_attribute_safe( + self.span, + GEN_AI_USAGE_OUTPUT_TOKENS, + self.output_tokens, + ) + + # Record metrics using accumulated data + span_attributes = { + GEN_AI_REQUEST_MODEL: self.model, + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + + # Create mock result object with accumulated data for metrics + class MockResult: + def __init__( + self, + response_id, + response_model, + service_tier, + input_tokens, + output_tokens, + ): + self.id = response_id + self.model = response_model + self.service_tier = service_tier + if input_tokens > 0 or output_tokens > 0: + self.usage = type( + "Usage", + (), + { + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "prompt_tokens": input_tokens, + "completion_tokens": output_tokens, + }, + )() + + mock_result = MockResult( + self.response_id, + self.response_model, + self.service_tier, + self.input_tokens, + self.output_tokens, + ) + + self.instrumentor._record_metrics( + operation_type="responses", + duration=duration, + result=mock_result, + span_attributes=span_attributes, + ) + + # End span with proper status + if self.span.span_instance.is_recording: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.OK + ) + self.span.span_instance.end() + self.span_ended = True + + def __iter__(self): + # Start streaming iteration + return self + + def __next__(self): + try: + chunk = next(self.stream_iter) + # Process chunk to accumulate data and maintain API compatibility + self.process_chunk(chunk) + # Also maintain backward compatibility with old accumulated_content + if hasattr(chunk, "output") and chunk.output: + self.accumulated_content.append(str(chunk.output)) + elif hasattr(chunk, "delta") and isinstance(chunk.delta, str): + self.accumulated_content.append(chunk.delta) + return chunk + except StopIteration: + # Stream is finished, perform cleanup + self.cleanup() + raise + except Exception as e: + # Error occurred, record metrics and set error status + if not self.span_ended: + duration = time.time() - self.start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: self.model, + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + if self.span.span_instance.is_recording: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + self.span.span_instance.record_exception(e) + self.span.span_instance.end() + self.span_ended = True + raise + + def _finalize_span(self): + """Finalize the span with accumulated content and end it.""" + if not self.span_ended: + duration = time.time() - self.start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: self.model, + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + ) + + if self.span.span_instance.is_recording: + # Note: For streaming responses, response metadata like tokens, finish_reasons + # are typically not available in individual chunks, so we focus on content. + + if self.accumulated_content: + full_content = "".join(self.accumulated_content) + self.instrumentor._add_message_event( + self.span, + role="assistant", + content=full_content, + conversation_id=self.conversation_id, + ) + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.OK + ) + self.span.span_instance.end() + self.span_ended = True + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + try: + self.cleanup() + except Exception: + pass # Don't let cleanup exceptions mask the original exception + return False + + def get_final_response(self): + """Proxy method to access the underlying stream's get_final_response if available.""" + if hasattr(self.stream_iter, "get_final_response"): + return self.stream_iter.get_final_response() + raise AttributeError("Underlying stream does not have 'get_final_response' method") + + return StreamWrapper( + stream, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ) + + def _wrap_response_stream_manager( + self, + stream_manager, + span: "AbstractSpan", + original_kwargs: Dict[str, Any], + start_time: float, + operation_name: str, + server_address: Optional[str], + port: Optional[int], + model: Optional[str], + ): + """Wrap a ResponseStreamManager to trace the stream when it's entered.""" + conversation_id = self._extract_conversation_id(original_kwargs) + instrumentor = self + + class ResponseStreamManagerWrapper: + """Wrapper for ResponseStreamManager that adds tracing to the underlying stream.""" + + def __init__( + self, + manager, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ): + self.manager = manager + self.span = span + self.conversation_id = conversation_id + self.instrumentor = instrumentor + self.start_time = start_time + self.operation_name = operation_name + self.server_address = server_address + self.port = port + self.model = model + self.wrapped_stream = None + + def __enter__(self): + # Enter the underlying ResponseStreamManager to get the ResponseStream + raw_stream = self.manager.__enter__() + # Wrap the ResponseStream with our tracing wrapper + self.wrapped_stream = self.instrumentor._wrap_streaming_response( + raw_stream, + self.span, + ({"conversation": self.conversation_id} if self.conversation_id else {}), + self.start_time, + self.operation_name, + self.server_address, + self.port, + self.model, + ) + return self.wrapped_stream + + def __exit__(self, exc_type, exc_val, exc_tb): + # Exit the underlying ResponseStreamManager + result = self.manager.__exit__(exc_type, exc_val, exc_tb) + return result + + return ResponseStreamManagerWrapper( + stream_manager, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ) + + def _wrap_async_streaming_response( + self, + stream, + span: "AbstractSpan", + original_kwargs: Dict[str, Any], + start_time: float, + operation_name: str, + server_address: Optional[str], + port: Optional[int], + model: Optional[str], + ): + """Wrap an async streaming response to trace chunks.""" + conversation_id = self._extract_conversation_id(original_kwargs) + + class AsyncStreamWrapper: # pylint: disable=too-many-instance-attributes,protected-access + def __init__( + self, + stream_async_iter, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ): + self.stream_async_iter = stream_async_iter + self.span = span + self.conversation_id = conversation_id + self.instrumentor = instrumentor + self.accumulated_content = [] + self.span_ended = False + self.start_time = start_time + self.operation_name = operation_name + self.server_address = server_address + self.port = port + self.model = model + + # Enhanced properties for sophisticated chunk processing + self.accumulated_output = [] + self.response_id = None + self.response_model = None + self.service_tier = None + self.input_tokens = 0 + self.output_tokens = 0 + self.finish_reason = None # Track finish_reason from streaming chunks + + # Track all output items from streaming events (tool calls, workflow actions, etc.) + # Use (id, type) as key to avoid overwriting when call and output have same ID + self.output_items = {} # Dict[(item_id, item_type), output_item] + self.has_output_items = False + + # Expose response attribute for compatibility with AsyncResponseStreamManager + self.response = getattr(stream_async_iter, "response", None) or getattr( + stream_async_iter, "_response", None + ) + + def append_output_content(self, content): + """Append content to accumulated output list.""" + if content: + self.accumulated_output.append(str(content)) + + def set_response_metadata(self, chunk): + """Update response metadata from chunk if not already set.""" + chunk_type = getattr(chunk, "type", None) + + if not self.response_id: + self.response_id = getattr(chunk, "id", None) + if not self.response_model: + self.response_model = getattr(chunk, "model", None) + if not self.service_tier: + self.service_tier = getattr(chunk, "service_tier", None) + + # Extract finish_reason from response.output_item.done events + if chunk_type == "response.output_item.done" and hasattr(chunk, "item"): + item = chunk.item + if hasattr(item, "status") and item.status: + self.finish_reason = item.status + # Also check for direct finish_reason attribute + elif hasattr(chunk, "finish_reason") and chunk.finish_reason: + self.finish_reason = chunk.finish_reason + # Also check for finish_details in output items (Azure AI Agents structure) + elif hasattr(chunk, "output") and chunk.output: + if isinstance(chunk.output, list) and len(chunk.output) > 0: + output_item = chunk.output[0] + if hasattr(output_item, "finish_details") and output_item.finish_details: + if hasattr(output_item.finish_details, "type"): + self.finish_reason = output_item.finish_details.type + + def process_chunk(self, chunk): + """Process chunk to accumulate data and update metadata.""" + # Check for output item events in streaming + chunk_type = getattr(chunk, "type", None) + + # Collect all complete output items from ResponseOutputItemDoneEvent or ResponseOutputItemAddedEvent + # This includes function_call, file_search_tool_call, code_interpreter_tool_call, + # web_search, mcp_call, computer_tool_call, custom_tool_call, workflow_action, and any future types + if (chunk_type in ("response.output_item.done", "response.output_item.added")) and hasattr( + chunk, "item" + ): + item = chunk.item + item_type = getattr(item, "type", None) + + # Collect any output item (tool calls, workflow actions, etc.) + if item_type: + # Use call_id, action_id, or id as the key (workflow actions use action_id) + item_id = ( + getattr(item, "call_id", None) + or getattr(item, "action_id", None) + or getattr(item, "id", None) + ) + if item_id: + # Use (id, type) tuple as key to distinguish call from output + self.output_items[(item_id, item_type)] = item + self.has_output_items = True + # Items without ID or type are skipped + + # Capture response ID from ResponseCreatedEvent or ResponseCompletedEvent + if chunk_type == "response.created" and hasattr(chunk, "response"): + if not self.response_id: + self.response_id = chunk.response.id + self.response_model = getattr(chunk.response, "model", None) + elif chunk_type == "response.completed" and hasattr(chunk, "response"): + if not self.response_id: + self.response_id = chunk.response.id + if not self.response_model: + self.response_model = getattr(chunk.response, "model", None) + # Extract usage from the completed response + if hasattr(chunk.response, "usage"): + response_usage = chunk.response.usage + if hasattr(response_usage, "input_tokens") and response_usage.input_tokens: + self.input_tokens = response_usage.input_tokens + if hasattr(response_usage, "output_tokens") and response_usage.output_tokens: + self.output_tokens = response_usage.output_tokens + # Also handle standard token field names for compatibility + if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens: + self.input_tokens = response_usage.prompt_tokens + if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens: + self.output_tokens = response_usage.completion_tokens + + # Only append TEXT content from delta events (not function call arguments or other deltas) + # Text deltas can come as: + # 1. response.text.delta - has delta as string + # 2. response.output_item.delta - has delta.text attribute + # Function call arguments come via response.function_call_arguments.delta - has delta as JSON string + # We need to avoid appending function call arguments + if chunk_type and ".delta" in chunk_type and hasattr(chunk, "delta"): + # If it's function_call_arguments.delta, skip it + if "function_call_arguments" not in chunk_type: + # Check if delta is a string (text content) or has .text attribute + if isinstance(chunk.delta, str): + self.append_output_content(chunk.delta) + elif hasattr(chunk.delta, "text"): + self.append_output_content(chunk.delta.text) + + # Always update metadata + self.set_response_metadata(chunk) + + # Handle usage info + usage = getattr(chunk, "usage", None) + if usage: + if hasattr(usage, "input_tokens") and usage.input_tokens: + self.input_tokens += usage.input_tokens + if hasattr(usage, "output_tokens") and usage.output_tokens: + self.output_tokens += usage.output_tokens + # Also handle standard token field names + if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: + self.input_tokens += usage.prompt_tokens + if hasattr(usage, "completion_tokens") and usage.completion_tokens: + self.output_tokens += usage.completion_tokens + + def cleanup(self): + """Perform final cleanup when streaming is complete.""" + if not self.span_ended: + duration = time.time() - self.start_time + + # Join all accumulated output content + complete_content = "".join(self.accumulated_output) + + if self.span.span_instance.is_recording: + # Add tool call events if we detected any output items (tool calls, etc.) + if self.has_output_items: + # Create mock response with output items for event generation + # The existing _add_tool_call_events method handles all tool types + mock_response = type( + "Response", + (), + {"output": list(self.output_items.values())}, + )() + self.instrumentor._add_tool_call_events( + self.span, + mock_response, + self.conversation_id, + ) + # Also add workflow action events + self.instrumentor._add_workflow_action_events( + self.span, + mock_response, + self.conversation_id, + ) + + # Only add assistant message event if there's actual text content (not empty/whitespace) + if complete_content and complete_content.strip(): + self.instrumentor._add_message_event( + self.span, + role="assistant", + content=complete_content, + conversation_id=self.conversation_id, + finish_reason=self.finish_reason, + ) + + # Set final span attributes using accumulated metadata + if self.response_id: + self.instrumentor._set_span_attribute_safe(self.span, GEN_AI_RESPONSE_ID, self.response_id) + if self.response_model: + self.instrumentor._set_span_attribute_safe( + self.span, GEN_AI_RESPONSE_MODEL, self.response_model + ) + + if self.service_tier: + self.instrumentor._set_span_attribute_safe( + self.span, + GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, + self.service_tier, + ) + + # Set token usage span attributes + if self.input_tokens > 0: + self.instrumentor._set_span_attribute_safe( + self.span, GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens + ) + if self.output_tokens > 0: + self.instrumentor._set_span_attribute_safe( + self.span, + GEN_AI_USAGE_OUTPUT_TOKENS, + self.output_tokens, + ) + + # Record metrics using accumulated data + span_attributes = { + GEN_AI_REQUEST_MODEL: self.model, + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + + # Create mock result object with accumulated data for metrics + class MockResult: + def __init__( + self, + response_id, + response_model, + service_tier, + input_tokens, + output_tokens, + ): + self.id = response_id + self.model = response_model + self.service_tier = service_tier + if input_tokens > 0 or output_tokens > 0: + self.usage = type( + "Usage", + (), + { + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "prompt_tokens": input_tokens, + "completion_tokens": output_tokens, + }, + )() + + mock_result = MockResult( + self.response_id, + self.response_model, + self.service_tier, + self.input_tokens, + self.output_tokens, + ) + + self.instrumentor._record_metrics( + operation_type="responses", + duration=duration, + result=mock_result, + span_attributes=span_attributes, + ) + + # End span with proper status + if self.span.span_instance.is_recording: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.OK + ) + self.span.span_instance.end() + self.span_ended = True + + def __aiter__(self): + return self + + async def __anext__(self): + try: + chunk = await self.stream_async_iter.__anext__() + # Process chunk to accumulate data and maintain API compatibility + self.process_chunk(chunk) + # Also maintain backward compatibility with old accumulated_content + if hasattr(chunk, "output") and chunk.output: + self.accumulated_content.append(str(chunk.output)) + elif hasattr(chunk, "delta") and isinstance(chunk.delta, str): + self.accumulated_content.append(chunk.delta) + return chunk + except StopAsyncIteration: + # Stream is finished, perform cleanup + self.cleanup() + raise + except Exception as e: + # Error occurred, record metrics and set error status + if not self.span_ended: + duration = time.time() - self.start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: self.model, + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + if self.span.span_instance.is_recording: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + self.span.span_instance.record_exception(e) + self.span.span_instance.end() + self.span_ended = True + raise + + def _finalize_span(self): + """Finalize the span with accumulated content and end it.""" + if not self.span_ended: + duration = time.time() - self.start_time + span_attributes = { + GEN_AI_REQUEST_MODEL: self.model, + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="responses", + duration=duration, + result=None, + span_attributes=span_attributes, + ) + + if self.span.span_instance.is_recording: + # Note: For streaming responses, response metadata like tokens, finish_reasons + # are typically not available in individual chunks, so we focus on content. + + if self.accumulated_content: + full_content = "".join(self.accumulated_content) + self.instrumentor._add_message_event( + self.span, + role="assistant", + content=full_content, + conversation_id=self.conversation_id, + ) + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.OK + ) + self.span.span_instance.end() + self.span_ended = True + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + try: + self.cleanup() + except Exception: + pass # Don't let cleanup exceptions mask the original exception + return False + + async def get_final_response(self): + """Proxy method to access the underlying stream's get_final_response if available.""" + if hasattr(self.stream_async_iter, "get_final_response"): + result = self.stream_async_iter.get_final_response() + # If it's a coroutine, await it + if hasattr(result, "__await__"): + return await result + return result + raise AttributeError("Underlying stream does not have 'get_final_response' method") + + return AsyncStreamWrapper( + stream, + span, + conversation_id, + self, + start_time, + operation_name, + server_address, + port, + model, + ) + + def _wrap_async_response_stream_manager( + self, + stream_manager, + span: "AbstractSpan", + original_kwargs: Dict[str, Any], + start_time: float, + operation_name: str, + server_address: Optional[str], + port: Optional[int], + model: Optional[str], + ): + """Wrap an AsyncResponseStreamManager to trace the stream when it's entered.""" + conversation_id = self._extract_conversation_id(original_kwargs) + instrumentor = self + + class AsyncResponseStreamManagerWrapper: + """Wrapper for AsyncResponseStreamManager that adds tracing to the underlying stream.""" + + def __init__( + self, + manager, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ): + self.manager = manager + self.span = span + self.conversation_id = conversation_id + self.instrumentor = instrumentor + self.start_time = start_time + self.operation_name = operation_name + self.server_address = server_address + self.port = port + self.model = model + self.wrapped_stream = None + + async def __aenter__(self): + # Enter the underlying AsyncResponseStreamManager to get the AsyncResponseStream + raw_stream = await self.manager.__aenter__() + # Wrap the AsyncResponseStream with our tracing wrapper + self.wrapped_stream = self.instrumentor._wrap_async_streaming_response( + raw_stream, + self.span, + ({"conversation": self.conversation_id} if self.conversation_id else {}), + self.start_time, + self.operation_name, + self.server_address, + self.port, + self.model, + ) + return self.wrapped_stream + + async def __aexit__(self, exc_type, exc_val, exc_tb): + # Exit the underlying AsyncResponseStreamManager + result = await self.manager.__aexit__(exc_type, exc_val, exc_tb) + return result + + return AsyncResponseStreamManagerWrapper( + stream_manager, + span, + conversation_id, + instrumentor, + start_time, + operation_name, + server_address, + port, + model, + ) + + def start_create_conversation_span( + self, + server_address: Optional[str] = None, + port: Optional[int] = None, + ) -> "Optional[AbstractSpan]": + """Start a span for create conversation API call.""" + span = start_span( + operation_name=OperationName.CREATE_CONVERSATION, + server_address=server_address, + port=port, + span_name=OperationName.CREATE_CONVERSATION.value, + gen_ai_provider=RESPONSES_PROVIDER, + ) + + if span and span.span_instance.is_recording: + self._set_span_attribute_safe(span, GEN_AI_OPERATION_NAME, OperationName.CREATE_CONVERSATION.value) + + return span + + def _create_conversations_span_from_parameters(self, *args, **kwargs): # pylint: disable=unused-argument + """Extract parameters and create span for conversations API tracing.""" + # Extract client from args (first argument) + client = args[0] if args else None + server_address, port = self._extract_server_info_from_client(client) + + # Create and return the span + return self.start_create_conversation_span( + server_address=server_address, + port=port, + ) + + def trace_conversations_create(self, function, *args, **kwargs): + """Trace synchronous conversations.create calls.""" + span = self._create_conversations_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + operation_name = "create_conversation" + + start_time = time.time() + + if span is None: + # Still record metrics even without spans + try: + result = function(*args, **kwargs) + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + raise + + with span: + try: + result = function(*args, **kwargs) + duration = time.time() - start_time + + # Extract and set conversation attributes + self._extract_conversation_attributes(span, result) + + # Record metrics using new dedicated method + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + span.span_instance.record_exception(e) + raise + + async def trace_conversations_create_async(self, function, *args, **kwargs): + """Trace asynchronous conversations.create calls.""" + span = self._create_conversations_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + operation_name = "create_conversation" + + start_time = time.time() + + if span is None: + # Still record metrics even without spans + try: + result = await function(*args, **kwargs) + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + raise + + with span: + try: + result = await function(*args, **kwargs) + duration = time.time() - start_time + + # Extract and set conversation attributes + self._extract_conversation_attributes(span, result) + + # Record metrics using new dedicated method + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=result, + span_attributes=span_attributes, + ) + + return result + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + span.span_instance.record_exception(e) + raise + + def start_list_conversation_items_span( + self, + server_address: Optional[str] = None, + port: Optional[int] = None, + conversation_id: Optional[str] = None, + ) -> "Optional[AbstractSpan]": + """Start a span for list conversation items API call.""" + span = start_span( + operation_name=OperationName.LIST_CONVERSATION_ITEMS, + server_address=server_address, + port=port, + span_name=OperationName.LIST_CONVERSATION_ITEMS.value, + gen_ai_provider=RESPONSES_PROVIDER, + ) + + if span and span.span_instance.is_recording: + # Set operation name attribute (start_span doesn't set this automatically) + self._set_attributes( + span, + (GEN_AI_OPERATION_NAME, OperationName.LIST_CONVERSATION_ITEMS.value), + ) + + # Set conversation-specific attributes that start_span doesn't handle + # Note: server_address is already set by start_span, so we don't need to set it again + self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) + + return span + + def _add_conversation_item_event( # pylint: disable=too-many-branches,too-many-locals + self, + span: "AbstractSpan", + item: Any, + ) -> None: + """Add a conversation item event to the span.""" + if not span or not span.span_instance.is_recording: + return + + # Extract basic item information + item_id = getattr(item, "id", None) + item_type = getattr(item, "type", "unknown") + role = getattr(item, "role", None) + + # Create event body - format depends on item type + event_body: List[Dict[str, Any]] = [] + + # Declare tool_call variable with type for use across branches + tool_call: Dict[str, Any] + + # Handle different item types + if item_type == "function_call_output": + # Function tool output - use optimized content format + role = "tool" # Override role for tool outputs + + tool_output: Dict[str, Any] = { + "type": item_type, + } + + # Add call_id as "id" - always include for correlation + if hasattr(item, "call_id"): + tool_output["id"] = item.call_id + elif hasattr(item, "id"): + tool_output["id"] = item.id + + # Add output field only if content recording is enabled + if _trace_responses_content: + if hasattr(item, "output"): + output_value = item.output + if isinstance(output_value, str): + try: + tool_output["output"] = json.loads(output_value) + except (json.JSONDecodeError, TypeError): + tool_output["output"] = output_value + else: + tool_output["output"] = output_value + + # Always include role and parts with type/id, only output when content recording enabled + event_body = [ + { + "role": role, + "parts": [{"type": "tool_call_output", "content": tool_output}], + } + ] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "function_call": + # Function tool call - use optimized content format + role = "assistant" # Override role for function calls + + tool_call = { + "type": item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "call_id"): + tool_call["id"] = item.call_id + elif hasattr(item, "id"): + tool_call["id"] = item.id + + # Only include function details if content recording is enabled + if _trace_responses_content: + # Add function details + if hasattr(item, "name"): + function_details: Dict[str, Any] = { + "name": item.name, + } + if hasattr(item, "arguments"): + # Parse arguments if it's a JSON string + args_value = item.arguments + if isinstance(args_value, str): + try: + function_details["arguments"] = json.loads(args_value) + except (json.JSONDecodeError, TypeError): + function_details["arguments"] = args_value + else: + function_details["arguments"] = args_value + + tool_call["function"] = function_details + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "file_search_call": + # File search tool call + role = "assistant" # Override role for file search calls + + tool_call = { + "type": item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "call_id"): + tool_call["id"] = item.call_id + elif hasattr(item, "id"): + tool_call["id"] = item.id + + # Only include file search details if content recording is enabled + if _trace_responses_content: + # Add file search details + file_search_details: Dict[str, Any] = {} + + if hasattr(item, "queries") and item.queries: + file_search_details["queries"] = item.queries + + if hasattr(item, "status"): + file_search_details["status"] = item.status + + if hasattr(item, "results") and item.results: + file_search_details["results"] = [ + { + "file_id": getattr(result, "file_id", None), + "file_name": getattr(result, "file_name", None), + "score": getattr(result, "score", None), + } + for result in item.results + ] + + if file_search_details: + tool_call["file_search"] = file_search_details + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "code_interpreter_call": + # Code interpreter tool call + role = "assistant" # Override role for code interpreter calls + + tool_call = { + "type": item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "call_id"): + tool_call["id"] = item.call_id + elif hasattr(item, "id"): + tool_call["id"] = item.id + + # Only include code interpreter details if content recording is enabled + if _trace_responses_content: + # Add code interpreter details + code_interpreter_details: Dict[str, Any] = {} + + if hasattr(item, "code") and item.code: + code_interpreter_details["code"] = item.code + + if hasattr(item, "status"): + code_interpreter_details["status"] = item.status + + if hasattr(item, "outputs") and item.outputs: + outputs_list = [] + for output in item.outputs: + output_type = getattr(output, "type", None) + if output_type == "logs": + outputs_list.append({"type": "logs", "logs": getattr(output, "logs", None)}) + elif output_type == "image": + # Use consistent "content" field for image data + outputs_list.append( + { + "type": "image", + "content": { + "file_id": getattr( + getattr(output, "image", None), + "file_id", + None, + ) + }, + } + ) + if outputs_list: + code_interpreter_details["outputs"] = outputs_list + + if code_interpreter_details: + tool_call["code_interpreter"] = code_interpreter_details + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "web_search_call": + # Web search tool call + role = "assistant" # Override role for web search calls + + tool_call = { + "type": item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "call_id"): + tool_call["id"] = item.call_id + elif hasattr(item, "id"): + tool_call["id"] = item.id + + # Only include web search details if content recording is enabled + if _trace_responses_content: + # Add web search details + web_search_details: Dict[str, Any] = {} + + if hasattr(item, "status"): + web_search_details["status"] = item.status + + if hasattr(item, "action") and item.action: + action_type = getattr(item.action, "type", None) + web_search_details["action_type"] = action_type + + if action_type == "search" and hasattr(item.action, "query"): + web_search_details["query"] = item.action.query + elif action_type == "open_page" and hasattr(item.action, "url"): + web_search_details["url"] = item.action.url + elif action_type == "find" and hasattr(item.action, "query"): + web_search_details["find_query"] = item.action.query + + if web_search_details: + tool_call["web_search"] = web_search_details + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "azure_ai_search_call": + # Azure AI Search tool call + role = "assistant" # Override role for Azure AI Search calls + + tool_call = { + "type": item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "call_id"): + tool_call["id"] = item.call_id + elif hasattr(item, "id"): + tool_call["id"] = item.id + + # Only include Azure AI Search details if content recording is enabled + if _trace_responses_content: + # Add Azure AI Search details + azure_ai_search_details: Dict[str, Any] = {} + + if hasattr(item, "status"): + azure_ai_search_details["status"] = item.status + + if hasattr(item, "input"): + azure_ai_search_details["input"] = item.input + + if hasattr(item, "results") and item.results: + azure_ai_search_details["results"] = [] + for result in item.results: + result_data = {} + if hasattr(result, "title"): + result_data["title"] = result.title + if hasattr(result, "url"): + result_data["url"] = result.url + if hasattr(result, "content"): + result_data["content"] = result.content + if result_data: + azure_ai_search_details["results"].append(result_data) + + if azure_ai_search_details: + tool_call["azure_ai_search"] = azure_ai_search_details + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "image_generation_call": + # Image generation tool call + role = "assistant" # Override role for image generation calls + + tool_call = { + "type": item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "call_id"): + tool_call["id"] = item.call_id + elif hasattr(item, "id"): + tool_call["id"] = item.id + + # Only include image generation details if content recording is enabled + if _trace_responses_content: + # Add image generation details + image_gen_details: Dict[str, Any] = {} + + if hasattr(item, "prompt"): + image_gen_details["prompt"] = item.prompt + + if hasattr(item, "quality"): + image_gen_details["quality"] = item.quality + + if hasattr(item, "size"): + image_gen_details["size"] = item.size + + if hasattr(item, "style"): + image_gen_details["style"] = item.style + + # Include the result (image data) only if binary data tracing is enabled + if _trace_binary_data and hasattr(item, "result") and item.result: + image_gen_details["result"] = item.result + + if image_gen_details: + tool_call["image_generation"] = image_gen_details + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "remote_function_call": + # Remote function call (like Bing Custom Search call) + role = "assistant" # Override role for remote function calls + + # Check if there's a more specific type in name field (e.g., "bing_custom_search_preview_call") + specific_type = None + if hasattr(item, "name") and item.name: + # Use the API type directly without transformation + specific_type = item.name + + tool_call = { + "type": specific_type if specific_type else item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "id"): + tool_call["id"] = item.id + elif hasattr(item, "call_id"): + tool_call["id"] = item.call_id + # Check model_extra for call_id + elif hasattr(item, "model_extra") and isinstance(item.model_extra, dict): + if "call_id" in item.model_extra: + tool_call["id"] = item.model_extra["call_id"] + + # Only include tool details if content recording is enabled + if _trace_responses_content: + # Extract data from model_extra if available (Pydantic v2 style) + if hasattr(item, "model_extra") and isinstance(item.model_extra, dict): + for key, value in item.model_extra.items(): + # Skip already captured fields, redundant fields (name, label), internal fields (partition_key), and empty/None values + if ( + key not in ["type", "id", "call_id", "name", "label", "partition_key"] + and value is not None + and value != "" + ): + tool_call[key] = value + + # Also try as_dict if available + if hasattr(item, "as_dict"): + try: + tool_dict = item.as_dict() + # Extract relevant fields (exclude already captured ones and empty/None values) + for key, value in tool_dict.items(): + if key not in [ + "type", + "id", + "call_id", + "name", + "label", + "role", + "content", + ]: + # Skip empty strings and None values + if value is not None and value != "": + # Don't overwrite if already exists + if key not in tool_call: + tool_call[key] = value + except Exception as e: + logger.debug(f"Failed to extract data from as_dict: {e}") + + # Fallback: try common fields directly (skip if empty and skip redundant name/label) + for field in [ + "input", + "arguments", + "status", + "error", + "search_query", + "query", + ]: + if hasattr(item, field): + try: + value = getattr(item, field) + if value is not None and value != "": + # If not already in tool_call, add it + if field not in tool_call: + tool_call[field] = value + except Exception: + pass + + # Include role in content for semantic convention compliance + event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "remote_function_call_output": + # Remote function call output (like Bing Custom Search output) + role = "tool" # Tool outputs use role "tool" + + # Check if there's a more specific type in name field (e.g., "bing_custom_search_preview_call_output") + specific_type = None + if hasattr(item, "name") and item.name: + # Use the API type directly without transformation + specific_type = item.name + + tool_output = { + "type": specific_type if specific_type else item_type, + } + + # Always include ID (needed for correlation) + if hasattr(item, "id"): + tool_output["id"] = item.id + elif hasattr(item, "call_id"): + tool_output["id"] = item.call_id + # Check model_extra for call_id + elif hasattr(item, "model_extra") and isinstance(item.model_extra, dict): + if "call_id" in item.model_extra: + tool_output["id"] = item.model_extra["call_id"] + + # Only include tool details if content recording is enabled + if _trace_responses_content: + # Extract data from model_extra if available (Pydantic v2 style) + if hasattr(item, "model_extra") and isinstance(item.model_extra, dict): + for key, value in item.model_extra.items(): + # Skip already captured fields, redundant fields (name, label), internal fields (partition_key), and empty/None values + if ( + key not in ["type", "id", "call_id", "name", "label", "partition_key"] + and value is not None + and value != "" + ): + tool_output[key] = value + + # Also try as_dict if available + if hasattr(item, "as_dict"): + try: + tool_dict = item.as_dict() + # Extract relevant fields (exclude already captured ones and empty/None values) + for key, value in tool_dict.items(): + if key not in [ + "type", + "id", + "call_id", + "name", + "label", + "role", + "content", + ]: + # Skip empty strings and None values + if value is not None and value != "": + # Don't overwrite if already exists + if key not in tool_output: + tool_output[key] = value + except Exception as e: + logger.debug(f"Failed to extract data from as_dict: {e}") + + # Fallback: try common fields directly (skip if empty and skip redundant name/label) + for field in [ + "input", + "output", + "results", + "status", + "error", + "search_query", + "query", + ]: + if hasattr(item, field): + try: + value = getattr(item, field) + if value is not None and value != "": + # If not already in tool_output, add it + if field not in tool_output: + tool_output[field] = value + except Exception: + pass + + # Tool outputs use tool_call_output type in parts + event_body = [{"role": role, "parts": [{"type": "tool_call_output", "content": tool_output}]}] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "workflow_action": + # Workflow action item - include workflow execution details + role = "workflow" + + # Extract workflow action attributes + status = getattr(item, "status", None) + + # Build workflow action details object + workflow_details: Dict[str, Any] = {} + + if status: + workflow_details["status"] = status + + # Only include action_id and previous_action_id when content recording is enabled + if _trace_responses_content: + action_id = getattr(item, "action_id", None) + previous_action_id = getattr(item, "previous_action_id", None) + + if action_id: + workflow_details["action_id"] = action_id + if previous_action_id: + workflow_details["previous_action_id"] = previous_action_id + + # Wrap in parts array for semantic convention compliance + parts: List[Dict[str, Any]] = [{"type": "workflow_action", "content": workflow_details}] + event_body = [{"role": role, "parts": parts}] + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + elif item_type == "message": + # Regular message - use content format for consistency + parts = [] + + # Always inspect content to determine types, regardless of recording setting + if hasattr(item, "content") and item.content: + for content_item in item.content: + content_type = getattr(content_item, "type", None) + + if content_type in ("input_text", "output_text", "text"): + if _trace_responses_content and hasattr(content_item, "text"): + # Include actual text content when recording is enabled + parts.append({"type": "text", "content": content_item.text}) + else: + # Type-only when recording is disabled + parts.append({"type": "text"}) + elif content_type == "input_image": + # Handle image content + image_part = {"type": "image"} + # Include image data if binary data tracing is enabled + # Note: The API typically doesn't return image_url in conversation items list, + # only in the original responses.create call + if _trace_binary_data: + image_url = getattr(content_item, "image_url", None) + if image_url: + # Use consistent format: content field directly contains the URL + image_part["content"] = image_url + parts.append(image_part) + elif content_type == "input_file": + # Handle file content + file_part: Dict[str, Any] = {"type": "file"} + file_content_dict: Dict[str, Any] = {} + filename = getattr(content_item, "filename", None) + if filename: + file_content_dict["filename"] = filename + file_id = getattr(content_item, "file_id", None) + if file_id: + file_content_dict["file_id"] = file_id + # Include file data if binary data tracing is enabled + if _trace_binary_data: + file_data = getattr(content_item, "file_data", None) + if file_data: + file_content_dict["file_data"] = file_data + if file_content_dict: + file_part["content"] = file_content_dict + parts.append(file_part) + + # Always create event_body with role and parts + role_obj: Dict[str, Any] = {"role": role} + if parts: + role_obj["parts"] = parts + + event_body = [role_obj] + + # Use conversation item event for all message items during listing + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + elif item_type and item_type.startswith("mcp"): + # MCP-specific item types (mcp_approval_request, mcp_list_tools, mcp_call, etc.) + # Determine role based on whether it's a response (user) or request/call (assistant) + if "response" in item_type: + # MCP responses (e.g., mcp_approval_response) are user inputs + mcp_role = "user" + else: + # MCP requests/calls (e.g., mcp_approval_request, mcp_list_tools, mcp_call) are assistant-initiated + mcp_role = "assistant" + + # Create structured event body + mcp_tool_call: Dict[str, Any] = { + "type": item_type, + } + + # Always include ID if available + if hasattr(item, "id"): + mcp_tool_call["id"] = item.id + elif hasattr(item, "call_id"): + mcp_tool_call["id"] = item.call_id + elif hasattr(item, "approval_request_id"): + # For approval responses, use the request ID + mcp_tool_call["id"] = item.approval_request_id + + # Only include additional details if content recording is enabled + if _trace_responses_content: + # Try to capture common MCP fields + for field in [ + "name", + "server_label", + "arguments", + "approval_request_id", + "approve", + "status", + ]: + if hasattr(item, field): + value = getattr(item, field) + if value is not None: + mcp_tool_call[field] = value + + # Wrap in parts array with appropriate role + event_body = [{"role": mcp_role, "parts": [{"type": "mcp", "content": mcp_tool_call}]}] + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + else: + # Unknown item type - create minimal event body with role if available + # This handles MCP tools and other future item types + else_role_obj: Dict[str, Any] = {} + if role: + else_role_obj["role"] = role + event_body = [else_role_obj] if else_role_obj else [] + + event_name = GEN_AI_CONVERSATION_ITEM_EVENT + + # Create event attributes + event_attributes = { + GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, + GEN_AI_CONVERSATION_ITEM_ID: item_id, + } + + # Commented out - message_role is now included in the event content instead + # # Add role attribute if present + # if role is not None: + # event_attributes[GEN_AI_CONVERSATION_ITEM_ROLE] = role + + # Use JSON format for event content (consistent with responses.create) + event_attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False) + + span.span_instance.add_event(name=event_name, attributes=event_attributes) + + def _wrap_conversation_items_list( + self, + result, + span: Optional["AbstractSpan"], + start_time: float, + operation_name: str, + server_address: Optional[str], + port: Optional[int], + ): + """Wrap the conversation items list result to add events for each item.""" + + class ItemsWrapper: + def __init__( + self, + items_result, + span, + instrumentor, + start_time, + operation_name, + server_address, + port, + ): + self.items_result = items_result + self.span = span + self.instrumentor = instrumentor + self.start_time = start_time + self.operation_name = operation_name + self.server_address = server_address + self.port = port + + def __iter__(self): + # For synchronous iteration + try: + for item in self.items_result: + if self.span: + self.instrumentor._add_conversation_item_event(self.span, item) + yield item + + # Record metrics when iteration is complete + duration = time.time() - self.start_time + span_attributes = { + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + ) + + # End span when iteration is complete + if self.span: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.OK + ) + self.span.span_instance.end() + except Exception as e: + # Record metrics for error case + duration = time.time() - self.start_time + span_attributes = { + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + + if self.span: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + self.span.span_instance.record_exception(e) + self.span.span_instance.end() + raise + + async def __aiter__(self): + # For asynchronous iteration + try: + async for item in self.items_result: + if self.span: + self.instrumentor._add_conversation_item_event(self.span, item) + yield item + + # Record metrics when iteration is complete + duration = time.time() - self.start_time + span_attributes = { + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + ) + + # End span when iteration is complete + if self.span: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.OK + ) + self.span.span_instance.end() + except Exception as e: + # Record metrics for error case + duration = time.time() - self.start_time + span_attributes = { + SERVER_ADDRESS: self.server_address, + SERVER_PORT: self.port, + } + self.instrumentor._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + + if self.span: + self.span.span_instance.set_status( + # pyright: ignore [reportPossiblyUnboundVariable] + StatusCode.ERROR, + str(e), + ) + self.span.span_instance.record_exception(e) + self.span.span_instance.end() + raise + + def __getattr__(self, name): + # Delegate other attributes to the original result + return getattr(self.items_result, name) + + return ItemsWrapper(result, span, self, start_time, operation_name, server_address, port) + + def _create_list_conversation_items_span_from_parameters(self, *args, **kwargs): + """Extract parameters and create span for list conversation items API tracing.""" + # Extract client from args (first argument) + client = args[0] if args else None + server_address, port = self._extract_server_info_from_client(client) + + # Extract conversation_id from kwargs + conversation_id = kwargs.get("conversation_id") + + return self.start_list_conversation_items_span( + server_address=server_address, + port=port, + conversation_id=conversation_id, + ) + + def trace_list_conversation_items(self, function, *args, **kwargs): + """Trace synchronous conversations.items.list calls.""" + span = self._create_list_conversation_items_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + operation_name = "list_conversation_items" + + start_time = time.time() + + if span is None: + # Still record metrics even without spans + try: + result = function(*args, **kwargs) + # For list operations, we can't measure duration until iteration is complete + # So we'll record metrics in the wrapper or during iteration + return self._wrap_conversation_items_list( + result, None, start_time, operation_name, server_address, port + ) + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + raise + + # Don't use context manager since we need the span to stay open during iteration + try: + result = function(*args, **kwargs) + + # Extract and set conversation items attributes + self._extract_conversation_items_attributes(span, result, args, kwargs) + + # Wrap the result to add events during iteration and handle span ending + wrapped_result = self._wrap_conversation_items_list( + result, span, start_time, operation_name, server_address, port + ) + + return wrapped_result + + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + # pyright: ignore [reportPossiblyUnboundVariable] + span.span_instance.set_status(StatusCode.ERROR, str(e)) + span.span_instance.record_exception(e) + span.span_instance.end() + raise + + async def trace_list_conversation_items_async(self, function, *args, **kwargs): + """Trace asynchronous conversations.items.list calls.""" + span = self._create_list_conversation_items_span_from_parameters(*args, **kwargs) + + # Extract parameters for metrics + server_address, port = self._extract_server_info_from_client(args[0] if args else None) + operation_name = "list_conversation_items" + + start_time = time.time() + + if span is None: + # Still record metrics even without spans + try: + result = await function(*args, **kwargs) + # For list operations, we can't measure duration until iteration is complete + # So we'll record metrics in the wrapper or during iteration + return self._wrap_conversation_items_list( + result, None, start_time, operation_name, server_address, port + ) + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + raise + + # Don't use context manager since we need the span to stay open during iteration + try: + result = await function(*args, **kwargs) + + # Extract and set conversation items attributes + self._extract_conversation_items_attributes(span, result, args, kwargs) + + # Wrap the result to add events during iteration and handle span ending + wrapped_result = self._wrap_conversation_items_list( + result, span, start_time, operation_name, server_address, port + ) + + return wrapped_result + + except Exception as e: + duration = time.time() - start_time + span_attributes = { + SERVER_ADDRESS: server_address, + SERVER_PORT: port, + } + self._record_metrics( + operation_type="conversation_items", + duration=duration, + result=None, + span_attributes=span_attributes, + error_type=str(type(e).__name__), + ) + # pyright: ignore [reportPossiblyUnboundVariable] + span.span_instance.set_status(StatusCode.ERROR, str(e)) + span.span_instance.record_exception(e) + span.span_instance.end() + raise + + def _trace_sync_function( + self, + function: Callable, + *, + _args_to_ignore: Optional[List[str]] = None, + _trace_type=TraceType.RESPONSES, + _name: Optional[str] = None, + ) -> Callable: + """ + Decorator that adds tracing to a synchronous function. + + :param function: The function to be traced. + :type function: Callable + :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. + :type: args_to_ignore: [List[str]], optional + :param trace_type: The type of the trace. Defaults to TraceType.RESPONSES. + :type trace_type: TraceType, optional + :param name: The name of the trace, will set to func name if not provided. + :type name: str, optional + :return: The traced function. + :rtype: Callable + """ + + @functools.wraps(function) + def inner(*args, **kwargs): + if _name == "create" and _trace_type == TraceType.RESPONSES: + return self.trace_responses_create(function, *args, **kwargs) + if _name == "stream" and _trace_type == TraceType.RESPONSES: + return self.trace_responses_stream(function, *args, **kwargs) + if _name == "create" and _trace_type == TraceType.CONVERSATIONS: + return self.trace_conversations_create(function, *args, **kwargs) + if _name == "list" and _trace_type == TraceType.CONVERSATIONS: + return self.trace_list_conversation_items(function, *args, **kwargs) + + return function(*args, **kwargs) + + return inner + + def _trace_async_function( + self, + function: Callable, + *, + _args_to_ignore: Optional[List[str]] = None, + _trace_type=TraceType.RESPONSES, + _name: Optional[str] = None, + ) -> Callable: + """ + Decorator that adds tracing to an asynchronous function. + + :param function: The function to be traced. + :type function: Callable + :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. + :type: args_to_ignore: [List[str]], optional + :param trace_type: The type of the trace. Defaults to TraceType.RESPONSES. + :type trace_type: TraceType, optional + :param name: The name of the trace, will set to func name if not provided. + :type name: str, optional + :return: The traced function. + :rtype: Callable + """ + + @functools.wraps(function) + async def inner(*args, **kwargs): + if _name == "create" and _trace_type == TraceType.RESPONSES: + return await self.trace_responses_create_async(function, *args, **kwargs) + if _name == "stream" and _trace_type == TraceType.RESPONSES: + # stream() is not async, just returns async context manager, so don't await + return self.trace_responses_stream_async(function, *args, **kwargs) + if _name == "create" and _trace_type == TraceType.CONVERSATIONS: + return await self.trace_conversations_create_async(function, *args, **kwargs) + if _name == "list" and _trace_type == TraceType.CONVERSATIONS: + return await self.trace_list_conversation_items_async(function, *args, **kwargs) + + return await function(*args, **kwargs) + + return inner + + def _inject_async(self, f, _trace_type, _name): + wrapper_fun = self._trace_async_function(f, _trace_type=_trace_type, _name=_name) + wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] + return wrapper_fun + + def _inject_sync(self, f, _trace_type, _name): + wrapper_fun = self._trace_sync_function(f, _trace_type=_trace_type, _name=_name) + wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] + return wrapper_fun + + def _responses_apis(self): + sync_apis = [] + async_apis = [] + + try: + import openai.resources.responses as responses_module + + if hasattr(responses_module, "Responses"): + sync_apis.append( + ( + responses_module.Responses, + "create", + TraceType.RESPONSES, + self._inject_sync, + "create", + ) + ) + # Add stream method + sync_apis.append( + ( + responses_module.Responses, + "stream", + TraceType.RESPONSES, + self._inject_sync, + "stream", + ) + ) + except ImportError: + pass + + try: + import openai.resources.responses as responses_module + + if hasattr(responses_module, "AsyncResponses"): + async_apis.append( + ( + responses_module.AsyncResponses, + "create", + TraceType.RESPONSES, + self._inject_async, + "create", + ) + ) + # Add stream method - note: stream() is not async, just returns async context manager + # So we use _inject_sync even though it's on AsyncResponses + sync_apis.append( + ( + responses_module.AsyncResponses, + "stream", + TraceType.RESPONSES, + self._inject_sync, + "stream", + ) + ) + except ImportError: + pass + + return sync_apis, async_apis + + def _conversations_apis(self): + sync_apis = [] + async_apis = [] + + try: + from openai.resources.conversations.conversations import Conversations + + sync_apis.append( + ( + Conversations, + "create", + TraceType.CONVERSATIONS, + self._inject_sync, + "create", + ) + ) + except ImportError: + pass + + try: + from openai.resources.conversations.conversations import AsyncConversations + + async_apis.append( + ( + AsyncConversations, + "create", + TraceType.CONVERSATIONS, + self._inject_async, + "create", + ) + ) + except ImportError: + pass + + # Add conversation items APIs + try: + from openai.resources.conversations.items import Items + + sync_apis.append( + ( + Items, + "list", + TraceType.CONVERSATIONS, + self._inject_sync, + "list", + ) + ) + except ImportError: + pass + + try: + from openai.resources.conversations.items import AsyncItems + + async_apis.append( + ( + AsyncItems, + "list", + TraceType.CONVERSATIONS, + self._inject_async, + "list", + ) + ) + except ImportError: + pass + + return sync_apis, async_apis + + def _responses_api_list(self): + sync_apis, async_apis = self._responses_apis() + yield from sync_apis + yield from async_apis + + def _conversations_api_list(self): + sync_apis, async_apis = self._conversations_apis() + yield from sync_apis + yield from async_apis + + def _all_api_list(self): + yield from self._responses_api_list() + yield from self._conversations_api_list() + + def _generate_api_and_injector(self, apis): + yield from apis + + def _available_responses_apis_and_injectors(self): + """ + Generates a sequence of tuples containing Responses and Conversations API classes, method names, and + corresponding injector functions. + + :return: A generator yielding tuples. + :rtype: tuple + """ + yield from self._generate_api_and_injector(self._all_api_list()) + + def _instrument_responses(self, enable_content_tracing: bool = False, enable_binary_data: bool = False): + """This function modifies the methods of the Responses API classes to + inject logic before calling the original methods. + The original methods are stored as _original attributes of the methods. + + :param enable_content_tracing: Indicates whether tracing of message content should be enabled. + This also controls whether function call tool function names, + parameter names and parameter values are traced. + :type enable_content_tracing: bool + :param enable_binary_data: Indicates whether tracing of binary data (such as images) should be enabled. + This only takes effect when content recording is also enabled. + :type enable_binary_data: bool + """ + # pylint: disable=W0603 + global _responses_traces_enabled + global _trace_responses_content + global _trace_binary_data + if _responses_traces_enabled: + return + + _responses_traces_enabled = True + _trace_responses_content = enable_content_tracing + _trace_binary_data = enable_binary_data + + # Initialize metrics instruments + self._initialize_metrics() + + for ( + api, + method, + trace_type, + injector, + name, + ) in self._available_responses_apis_and_injectors(): + try: + setattr(api, method, injector(getattr(api, method), trace_type, name)) + except (AttributeError, ImportError) as e: + logger.debug(f"Could not instrument {api.__name__}.{method}: {e}") + + def _uninstrument_responses(self): + global _responses_traces_enabled + global _trace_responses_content + if not _responses_traces_enabled: + return + + _responses_traces_enabled = False + _trace_responses_content = False + for ( + api, + method, + trace_type, + injector, + name, + ) in self._available_responses_apis_and_injectors(): + try: + original_method = getattr(getattr(api, method), "_original", None) + if original_method: + setattr(api, method, original_method) + except (AttributeError, ImportError): + pass + + def _is_instrumented(self): + global _responses_traces_enabled + return _responses_traces_enabled + + def _set_enable_content_recording(self, enable_content_recording: bool = False) -> None: + global _trace_responses_content + _trace_responses_content = enable_content_recording + + def _is_content_recording_enabled(self) -> bool: + global _trace_responses_content + return _trace_responses_content + + def _set_enable_binary_data(self, enable_binary_data: bool = False) -> None: + global _trace_binary_data + _trace_binary_data = enable_binary_data + + def _is_binary_data_enabled(self) -> bool: + global _trace_binary_data + return _trace_binary_data + + def record_error(self, span, exc): + # pyright: ignore [reportPossiblyUnboundVariable] + span.span_instance.set_status(StatusCode.ERROR, str(exc)) + span.span_instance.record_exception(exc) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py new file mode 100644 index 000000000000..956b43792d71 --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py @@ -0,0 +1,206 @@ +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import functools +import inspect +from typing import Any, Callable, Optional, Dict + +try: + # pylint: disable = no-name-in-module + from opentelemetry import trace as opentelemetry_trace + + _tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] + _tracing_library_available = True +except ModuleNotFoundError: + _tracing_library_available = False + +if _tracing_library_available: + + def trace_function(span_name: Optional[str] = None): + """ + A decorator for tracing function calls using OpenTelemetry. + + This decorator handles various data types for function parameters and return values, + and records them as attributes in the trace span. The supported data types include: + - Basic data types: str, int, float, bool + - Collections: list, dict, tuple, set + + Special handling for collections: + - If a collection (list, dict, tuple, set) contains nested collections, the entire collection + is converted to a string before being recorded as an attribute. + - Sets and dictionaries are always converted to strings to ensure compatibility with span attributes. + + Object types are omitted, and the corresponding parameter is not traced. + + :param span_name: The name of the span. If not provided, the function name is used. + :type span_name: Optional[str] + :return: The decorated function with tracing enabled. + :rtype: Callable + """ + + def decorator(func: Callable) -> Callable: + @functools.wraps(func) + async def async_wrapper(*args: Any, **kwargs: Any) -> Any: + """ + Wrapper function for asynchronous functions. + + :param args: Positional arguments passed to the function. + :type args: Tuple[Any] + :return: The result of the decorated asynchronous function. + :rtype: Any + """ + tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] + name = span_name if span_name else func.__name__ + with tracer.start_as_current_span(name) as span: + try: + # Sanitize parameters and set them as attributes + sanitized_params = sanitize_parameters(func, *args, **kwargs) + span.set_attributes(sanitized_params) + result = await func(*args, **kwargs) + sanitized_result = sanitize_for_attributes(result) + if sanitized_result is not None: + if isinstance(sanitized_result, (list, dict, tuple, set)): + if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_result): + sanitized_result = str(sanitized_result) + span.set_attribute("code.function.return.value", sanitized_result) # type: ignore + return result + except Exception as e: + span.record_exception(e) + span.set_attribute("error.type", e.__class__.__qualname__) # type: ignore + raise + + @functools.wraps(func) + def sync_wrapper(*args: Any, **kwargs: Any) -> Any: + """ + Wrapper function for synchronous functions. + + :param args: Positional arguments passed to the function. + :type args: Tuple[Any] + :return: The result of the decorated synchronous function. + :rtype: Any + """ + tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] + name = span_name if span_name else func.__name__ + with tracer.start_as_current_span(name) as span: + try: + # Sanitize parameters and set them as attributes + sanitized_params = sanitize_parameters(func, *args, **kwargs) + span.set_attributes(sanitized_params) + result = func(*args, **kwargs) + sanitized_result = sanitize_for_attributes(result) + if sanitized_result is not None: + if isinstance(sanitized_result, (list, dict, tuple, set)): + if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_result): + sanitized_result = str(sanitized_result) + span.set_attribute("code.function.return.value", sanitized_result) # type: ignore + return result + except Exception as e: + span.record_exception(e) + span.set_attribute("error.type", e.__class__.__qualname__) # type: ignore + raise + + # Determine if the function is async + if inspect.iscoroutinefunction(func): + return async_wrapper + return sync_wrapper + + return decorator + +else: + # Define a no-op decorator if OpenTelemetry is not available + def trace_function(span_name: Optional[str] = None): # pylint: disable=unused-argument + """ + A no-op decorator for tracing function calls when OpenTelemetry is not available. + + :param span_name: Not used in this version. + :type span_name: Optional[str] + :return: The original function. + :rtype: Callable + """ + + def decorator(func: Callable) -> Callable: + return func + + return decorator + + +def sanitize_parameters(func, *args, **kwargs) -> Dict[str, Any]: + """ + Sanitize function parameters to include only built-in data types. + + :param func: The function being decorated. + :type func: Callable + :param args: Positional arguments passed to the function. + :type args: Tuple[Any] + :return: A dictionary of sanitized parameters. + :rtype: Dict[str, Any] + """ + params = inspect.signature(func).parameters + sanitized_params = {} + + for i, (name, param) in enumerate(params.items()): + if i < len(args): + # Use positional argument if provided + value = args[i] + else: + # Use keyword argument if provided, otherwise fall back to default value + value = kwargs.get(name, param.default) + + sanitized_value = sanitize_for_attributes(value) + # Check if the collection has nested collections + if isinstance(sanitized_value, (list, dict, tuple, set)): + if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_value): + sanitized_value = str(sanitized_value) + if sanitized_value is not None: + sanitized_params["code.function.parameter." + name] = sanitized_value + + return sanitized_params + + +# pylint: disable=R0911 +def sanitize_for_attributes(value: Any, is_recursive: bool = False) -> Any: + """ + Sanitize a value to be used as an attribute. + + :param value: The value to sanitize. + :type value: Any + :param is_recursive: Indicates if the function is being called recursively. Default is False. + :type is_recursive: bool + :return: The sanitized value or None if the value is not a supported type. + :rtype: Any + """ + if isinstance(value, (str, int, float, bool)): + return value + if isinstance(value, list): + return [ + sanitize_for_attributes(item, True) + for item in value + if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) + ] + if isinstance(value, dict): + retval = { + k: sanitize_for_attributes(v, True) + for k, v in value.items() + if isinstance(v, (str, int, float, bool, list, dict, tuple, set)) + } + # dict to compatible with span attribute, so return it as a string + if is_recursive: + return retval + return str(retval) + if isinstance(value, tuple): + return tuple( + sanitize_for_attributes(item, True) + for item in value + if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) + ) + if isinstance(value, set): + retval_set = { + sanitize_for_attributes(item, True) + for item in value + if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) + } + if is_recursive: + return retval_set + return str(retval_set) + return None diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py new file mode 100644 index 000000000000..931c3d2abf7b --- /dev/null +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py @@ -0,0 +1,278 @@ +# pylint: disable=line-too-long,useless-suppression +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from typing import Optional +import logging +from enum import Enum + +from azure.core.tracing import AbstractSpan, SpanKind # type: ignore +from azure.core.settings import settings # type: ignore + +try: + _span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable +except ModuleNotFoundError: + _span_impl_type = None + +logger = logging.getLogger(__name__) + + +GEN_AI_MESSAGE_ID = "gen_ai.message.id" +GEN_AI_MESSAGE_STATUS = "gen_ai.message.status" +GEN_AI_THREAD_ID = "gen_ai.thread.id" +GEN_AI_THREAD_RUN_ID = "gen_ai.thread.run.id" +GEN_AI_AGENT_ID = "gen_ai.agent.id" +GEN_AI_AGENT_NAME = "gen_ai.agent.name" +GEN_AI_AGENT_DESCRIPTION = "gen_ai.agent.description" +GEN_AI_OPERATION_NAME = "gen_ai.operation.name" +GEN_AI_THREAD_RUN_STATUS = "gen_ai.thread.run.status" +GEN_AI_REQUEST_MODEL = "gen_ai.request.model" +GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature" +GEN_AI_REQUEST_TOP_P = "gen_ai.request.top_p" +GEN_AI_REQUEST_MAX_INPUT_TOKENS = "gen_ai.request.max_input_tokens" +GEN_AI_REQUEST_MAX_OUTPUT_TOKENS = "gen_ai.request.max_output_tokens" +GEN_AI_RESPONSE_MODEL = "gen_ai.response.model" +GEN_AI_SYSTEM = "gen_ai.system" +SERVER_ADDRESS = "server.address" +SERVER_PORT = "server.port" +AZ_AI_AGENT_SYSTEM = "az.ai.agents" +AZURE_AI_AGENTS = "azure.ai.agents" +AZ_NAMESPACE = "az.namespace" +AZ_NAMESPACE_VALUE = "Microsoft.CognitiveServices" +GEN_AI_TOOL_NAME = "gen_ai.tool.name" +GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id" +GEN_AI_REQUEST_RESPONSE_FORMAT = "gen_ai.request.response_format" +GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens" +GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens" +GEN_AI_SYSTEM_MESSAGE = "gen_ai.system_instructions" +GEN_AI_EVENT_CONTENT = "gen_ai.event.content" +GEN_AI_RUN_STEP_START_TIMESTAMP = "gen_ai.run_step.start.timestamp" +GEN_AI_RUN_STEP_END_TIMESTAMP = "gen_ai.run_step.end.timestamp" +GEN_AI_RUN_STEP_STATUS = "gen_ai.run_step.status" +ERROR_TYPE = "error.type" +ERROR_MESSAGE = "error.message" +GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION = "1.34.0" +GEN_AI_PROVIDER_NAME = "gen_ai.provider.name" + +# Added to the latest version, not part of semantic conventions +GEN_AI_REQUEST_REASONING_EFFORT = "gen_ai.request.reasoning.effort" +GEN_AI_REQUEST_REASONING_SUMMARY = "gen_ai.request.reasoning.summary" +GEN_AI_REQUEST_STRUCTURED_INPUTS = "gen_ai.request.structured_inputs" +GEN_AI_AGENT_VERSION = "gen_ai.agent.version" + +# Additional attribute names +GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id" +GEN_AI_CONVERSATION_ITEM_ID = "gen_ai.conversation.item.id" +GEN_AI_CONVERSATION_ITEM_ROLE = "gen_ai.conversation.item.role" +GEN_AI_REQUEST_TOOLS = "gen_ai.request.tools" +GEN_AI_RESPONSE_ID = "gen_ai.response.id" +GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT = "gen_ai.openai.response.system_fingerprint" +GEN_AI_OPENAI_RESPONSE_SERVICE_TIER = "gen_ai.openai.response.service_tier" +GEN_AI_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens" +GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons" +GEN_AI_RESPONSE_OBJECT = "gen_ai.response.object" +GEN_AI_TOKEN_TYPE = "gen_ai.token.type" +GEN_AI_MESSAGE_ROLE = "gen_ai.message.role" +GEN_AI_AGENT_TYPE = "gen_ai.agent.type" +GEN_AI_CONVERSATION_ITEM_TYPE = "gen_ai.conversation.item.type" +GEN_AI_AGENT_HOSTED_CPU = "gen_ai.agent.hosted.cpu" +GEN_AI_AGENT_HOSTED_MEMORY = "gen_ai.agent.hosted.memory" +GEN_AI_AGENT_HOSTED_IMAGE = "gen_ai.agent.hosted.image" +GEN_AI_AGENT_HOSTED_PROTOCOL = "gen_ai.agent.hosted.protocol" +GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION = "gen_ai.agent.hosted.protocol_version" + +# Event names +GEN_AI_USER_MESSAGE_EVENT = "gen_ai.input.messages" +GEN_AI_ASSISTANT_MESSAGE_EVENT = "gen_ai.output.messages" +GEN_AI_TOOL_MESSAGE_EVENT = "gen_ai.input.messages" # Keep separate constant but use same value as user messages +GEN_AI_WORKFLOW_ACTION_EVENT = "gen_ai.workflow.action" +GEN_AI_CONVERSATION_ITEM_EVENT = "gen_ai.conversation.item" +GEN_AI_SYSTEM_INSTRUCTION_EVENT = "gen_ai.system.instructions" +GEN_AI_AGENT_WORKFLOW_EVENT = "gen_ai.agent.workflow" + +# Attribute names for messages (when USE_MESSAGE_EVENTS = False) +GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages" +GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages" + +# Metric names +GEN_AI_CLIENT_OPERATION_DURATION = "gen_ai.client.operation.duration" +GEN_AI_CLIENT_TOKEN_USAGE = "gen_ai.client.token.usage" + +# Constant attribute values +AZURE_AI_AGENTS_SYSTEM = "az.ai.agents" +AGENTS_PROVIDER = "microsoft.foundry" +RESPONSES_PROVIDER = "microsoft.foundry" +AGENT_TYPE_PROMPT = "prompt" +AGENT_TYPE_WORKFLOW = "workflow" +AGENT_TYPE_HOSTED = "hosted" +AGENT_TYPE_UNKNOWN = "unknown" + +# Span name prefixes for responses API operations +SPAN_NAME_INVOKE_AGENT = "invoke_agent" +SPAN_NAME_CHAT = "chat" + +# Operation names for gen_ai.operation.name attribute +OPERATION_NAME_INVOKE_AGENT = "invoke_agent" +OPERATION_NAME_CHAT = "chat" + +# Configuration: Controls whether input/output messages are emitted as events or attributes +# Can be set at runtime for testing purposes (internal use only) +# Set to True for event-based, False for attribute-based (default) +_use_message_events = False + +# Configuration: Controls whether function tool calls use simplified OTEL-compliant format +# When True (default), function_call and function_call_output use a simpler structure: +# tool_call: {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} +# tool_call_response: {"type": "tool_call_response", "id": "...", "result": "..."} +# When False, the full nested structure is used +_use_simple_tool_format = True + + +def _get_use_simple_tool_format() -> bool: + """Get the current tool format mode (simple vs nested). Internal use only. + + :return: True if using simple OTEL-compliant format, False if using nested format + :rtype: bool + """ + return _use_simple_tool_format + + +def _set_use_simple_tool_format(use_simple: bool) -> None: + """ + Set the tool format mode at runtime. Internal use only. + + :param use_simple: True to use simple OTEL-compliant format, False for nested format + :type use_simple: bool + """ + global _use_simple_tool_format # pylint: disable=global-statement + _use_simple_tool_format = use_simple + + +def _get_use_message_events() -> bool: + """Get the current message tracing mode (events vs attributes). Internal use only. + + :return: True if using events, False if using attributes + :rtype: bool + """ + return _use_message_events + + +def _set_use_message_events(use_events: bool) -> None: + """ + Set the message tracing mode at runtime. Internal use only. + + :param use_events: True to use events (default), False to use attributes + :type use_events: bool + """ + global _use_message_events # pylint: disable=global-statement + _use_message_events = use_events + + +class OperationName(Enum): + CREATE_AGENT = "create_agent" + CREATE_THREAD = "create_thread" + CREATE_MESSAGE = "create_message" + START_THREAD_RUN = "start_thread_run" + GET_THREAD_RUN = "get_thread_run" + EXECUTE_TOOL = "execute_tool" + LIST_MESSAGES = "list_messages" + LIST_RUN_STEPS = "list_run_steps" + SUBMIT_TOOL_OUTPUTS = "submit_tool_outputs" + PROCESS_THREAD_RUN = "process_thread_run" + RESPONSES = "responses" + CREATE_CONVERSATION = "create_conversation" + LIST_CONVERSATION_ITEMS = "list_conversation_items" + + +def start_span( + operation_name: OperationName, + server_address: Optional[str], + port: Optional[int] = None, + span_name: Optional[str] = None, + thread_id: Optional[str] = None, + agent_id: Optional[str] = None, + run_id: Optional[str] = None, + model: Optional[str] = None, + temperature: Optional[float] = None, + top_p: Optional[float] = None, + max_prompt_tokens: Optional[int] = None, + max_completion_tokens: Optional[int] = None, + response_format: Optional[str] = None, + reasoning: Optional[str] = None, # pylint: disable=unused-argument + reasoning_effort: Optional[str] = None, + reasoning_summary: Optional[str] = None, + structured_inputs: Optional[str] = None, + gen_ai_system: Optional[str] = None, + gen_ai_provider: Optional[str] = AGENTS_PROVIDER, + kind: SpanKind = SpanKind.CLIENT, +) -> "Optional[AbstractSpan]": + global _span_impl_type # pylint: disable=global-statement + if _span_impl_type is None: + # Try to reinitialize the span implementation type. + # This is a workaround for the case when the tracing implementation is not set up yet when the agent telemetry is imported. + # This code should not even get called if settings.tracing_implementation() returns None since that is also checked in + # _trace_sync_function and _trace_async_function functions in the AIProjectInstrumentor. + _span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable + if _span_impl_type is None: + return None + + span = _span_impl_type( + name=span_name or operation_name.value, + kind=kind, + schema_version=GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION, + ) + + if span and span.span_instance.is_recording: + span.add_attribute(AZ_NAMESPACE, AZ_NAMESPACE_VALUE) + span.add_attribute(GEN_AI_PROVIDER_NAME, AGENTS_PROVIDER) + + if gen_ai_provider: + span.add_attribute(GEN_AI_PROVIDER_NAME, gen_ai_provider) + + if gen_ai_system: + span.add_attribute(GEN_AI_SYSTEM, gen_ai_system) + + if server_address: + span.add_attribute(SERVER_ADDRESS, server_address) + + if port is not None and port != 443: + span.add_attribute(SERVER_PORT, port) + + if thread_id: + span.add_attribute(GEN_AI_THREAD_ID, thread_id) + + if agent_id: + span.add_attribute(GEN_AI_AGENT_ID, agent_id) + + if run_id: + span.add_attribute(GEN_AI_THREAD_RUN_ID, run_id) + + if model: + span.add_attribute(GEN_AI_REQUEST_MODEL, model) + + if temperature: + span.add_attribute(GEN_AI_REQUEST_TEMPERATURE, str(temperature)) + + if top_p: + span.add_attribute(GEN_AI_REQUEST_TOP_P, str(top_p)) + + if max_prompt_tokens: + span.add_attribute(GEN_AI_REQUEST_MAX_INPUT_TOKENS, max_prompt_tokens) + + if max_completion_tokens: + span.add_attribute(GEN_AI_REQUEST_MAX_OUTPUT_TOKENS, max_completion_tokens) + + if response_format: + span.add_attribute(GEN_AI_REQUEST_RESPONSE_FORMAT, response_format) + + if reasoning_effort: + span.add_attribute(GEN_AI_REQUEST_REASONING_EFFORT, reasoning_effort) + + if reasoning_summary: + span.add_attribute(GEN_AI_REQUEST_REASONING_SUMMARY, reasoning_summary) + + if structured_inputs: + span.add_attribute(GEN_AI_REQUEST_STRUCTURED_INPUTS, structured_inputs) + + return span diff --git a/sdk/ai/azure-ai-projects/pyproject.toml b/sdk/ai/azure-ai-projects/pyproject.toml index fe16fe054548..26eac48e1123 100644 --- a/sdk/ai/azure-ai-projects/pyproject.toml +++ b/sdk/ai/azure-ai-projects/pyproject.toml @@ -14,10 +14,10 @@ name = "azure-ai-projects" authors = [ { name = "Microsoft Corporation", email = "azpysdkhelp@microsoft.com" }, ] -description = "Microsoft Corporation Azure Ai Projects Client Library for Python" +description = "Microsoft Corporation Azure AI Projects Client Library for Python" license = "MIT" classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", @@ -26,6 +26,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] requires-python = ">=3.9" keywords = ["azure", "azure sdk"] @@ -43,7 +44,7 @@ dynamic = [ ] [project.urls] -repository = "https://github.com/Azure/azure-sdk-for-python" +repository = "https://aka.ms/azsdk/azure-ai-projects-v2/python/code" [tool.setuptools.dynamic] version = {attr = "azure.ai.projects._version.VERSION"} @@ -51,13 +52,13 @@ readme = {file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown"} [tool.setuptools.packages.find] exclude = [ - "tests*", + "azure.ai", + "azure", + "doc*", + "generated_samples*", "generated_tests*", "samples*", - "generated_samples*", - "doc*", - "azure", - "azure.ai", + "tests*", ] [tool.setuptools.package-data] @@ -68,3 +69,4 @@ verifytypes = false [tool.azure-sdk-conda] in_bundle = false + diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index c4fa71fce5b3..01ecabb49ebf 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: 57e7a9ebf3c779e4f8c90b9e140bf09c14aa0830 +commit: e50321286a093e4cd6c77edfa7162d73d142f594 repo: Azure/azure-rest-api-specs additionalDirectories: From d55d0058ea823309f6681dcc269788f0f9aebed5 Mon Sep 17 00:00:00 2001 From: azure-sdk Date: Thu, 26 Mar 2026 23:08:04 +0000 Subject: [PATCH 36/36] Configurations: 'specification/ai-foundry/data-plane/Foundry/tspconfig.yaml', API Version: v1, SDK Release Type: stable, and CommitSHA: 'bd70f1fbb6f1691a3107d791c7aa5d1dede46f01' in SpecRepo: 'https://github.com/Azure/azure-rest-api-specs' Pipeline run: https://dev.azure.com/azure-sdk/internal/_build/results?buildId=6070083 Refer to https://eng.ms/docs/products/azure-developer-experience/develop/sdk-release/sdk-release-prerequisites to prepare for SDK release. --- sdk/ai/azure-ai-projects/CHANGELOG.md | 4 + sdk/ai/azure-ai-projects/_metadata.json | 6 +- .../azure/ai/projects/_patch.py | 321 +- .../azure/ai/projects/_validation.py | 66 - .../azure/ai/projects/_version.py | 2 +- .../azure/ai/projects/aio/_patch.py | 297 +- .../ai/projects/aio/operations/_operations.py | 39 +- .../ai/projects/aio/operations/_patch.py | 84 +- .../aio/operations/_patch_agents_async.py | 192 - .../operations/_patch_connections_async.py | 74 - .../aio/operations/_patch_datasets_async.py | 223 - .../_patch_evaluation_rules_async.py | 129 - .../aio/operations/_patch_evaluators_async.py | 258 - .../aio/operations/_patch_memories_async.py | 379 -- .../aio/operations/_patch_telemetry_async.py | 75 - .../azure/ai/projects/models/_enums.py | 8 +- .../azure/ai/projects/models/_models.py | 18 +- .../azure/ai/projects/models/_patch.py | 352 +- .../ai/projects/operations/_operations.py | 39 +- .../azure/ai/projects/operations/_patch.py | 139 +- .../ai/projects/operations/_patch_agents.py | 218 - .../projects/operations/_patch_connections.py | 63 - .../ai/projects/operations/_patch_datasets.py | 222 - .../operations/_patch_evaluation_rules.py | 130 - .../projects/operations/_patch_evaluators.py | 258 - .../ai/projects/operations/_patch_memories.py | 414 -- .../projects/operations/_patch_telemetry.py | 69 - .../azure/ai/projects/telemetry/__init__.py | 12 - .../telemetry/_ai_project_instrumentor.py | 1535 ------ .../telemetry/_responses_instrumentor.py | 4883 ----------------- .../ai/projects/telemetry/_trace_function.py | 206 - .../azure/ai/projects/telemetry/_utils.py | 278 - sdk/ai/azure-ai-projects/pyproject.toml | 18 +- sdk/ai/azure-ai-projects/tsp-location.yaml | 2 +- 34 files changed, 80 insertions(+), 10933 deletions(-) delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py delete mode 100644 sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py diff --git a/sdk/ai/azure-ai-projects/CHANGELOG.md b/sdk/ai/azure-ai-projects/CHANGELOG.md index b254a155fd3d..a6bbf0926b62 100644 --- a/sdk/ai/azure-ai-projects/CHANGELOG.md +++ b/sdk/ai/azure-ai-projects/CHANGELOG.md @@ -1,5 +1,9 @@ # Release History +## 2.1.0 (2026-03-26) + +skip changelog generation for data-plane package and please add changelog manually. + ## 2.0.2 (Unreleased) ### Features Added diff --git a/sdk/ai/azure-ai-projects/_metadata.json b/sdk/ai/azure-ai-projects/_metadata.json index 3a000fe50d57..df3ff54daaf5 100644 --- a/sdk/ai/azure-ai-projects/_metadata.json +++ b/sdk/ai/azure-ai-projects/_metadata.json @@ -2,5 +2,9 @@ "apiVersion": "v1", "apiVersions": { "Azure.AI.Projects": "v1" - } + }, + "commit": "bd70f1fbb6f1691a3107d791c7aa5d1dede46f01", + "repository_url": "https://github.com/Azure/azure-rest-api-specs", + "typespec_src": "specification/ai-foundry/data-plane/Foundry", + "emitterVersion": "0.61.1" } \ No newline at end of file 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 0b3aca9db700..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -1,324 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import os -import re -import logging -from typing import List, Any, Union -import httpx # pylint: disable=networking-import-outside-azure-core-transport -from openai import OpenAI -from azure.core.credentials import AzureKeyCredential -from azure.core.tracing.decorator import distributed_trace -from azure.core.credentials import TokenCredential -from azure.identity import get_bearer_token_provider -from ._client import AIProjectClient as AIProjectClientGenerated -from .operations import TelemetryOperations -logger = logging.getLogger(__name__) - - -class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-instance-attributes - """AIProjectClient. - - :ivar beta: BetaOperations operations - :vartype beta: azure.ai.projects.operations.BetaOperations - :ivar agents: AgentsOperations operations - :vartype agents: azure.ai.projects.operations.AgentsOperations - :ivar evaluation_rules: EvaluationRulesOperations operations - :vartype evaluation_rules: azure.ai.projects.operations.EvaluationRulesOperations - :ivar connections: ConnectionsOperations operations - :vartype connections: azure.ai.projects.operations.ConnectionsOperations - :ivar datasets: DatasetsOperations operations - :vartype datasets: azure.ai.projects.operations.DatasetsOperations - :ivar deployments: DeploymentsOperations operations - :vartype deployments: azure.ai.projects.operations.DeploymentsOperations - :ivar indexes: IndexesOperations operations - :vartype indexes: azure.ai.projects.operations.IndexesOperations - :param endpoint: Foundry Project endpoint in the form - "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you - only have one Project in your Foundry Hub, or to target the default Project in your Hub, use - the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". - Required. - :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Is either a key - credential type or a token credential type. Required. - :type credential: ~azure.core.credentials.AzureKeyCredential or - ~azure.core.credentials.TokenCredential - :param allow_preview: Whether to enable preview features. Optional, default is False. - Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) - or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). - Set this to True to use human evaluation rule action (class :class:`~azure.ai.projects.models.HumanEvaluationPreviewRuleAction`). - Methods on the `.beta` sub-client (class :class:`~azure.ai.projects.operations.BetaOperations`) - are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. - When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` - with the appropriate value in all relevant calls to the service. - :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1". Default - value is "v1". Note that overriding this default value may result in unsupported behavior. - :paramtype api_version: str - """ - - def __init__( - self, - endpoint: str, - credential: Union[AzureKeyCredential, "TokenCredential"], - *, - allow_preview: bool = False, - **kwargs: Any, - ) -> None: - - self._console_logging_enabled: bool = ( - os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" - ) - - if self._console_logging_enabled: - import sys - - # Enable detailed console logs across Azure libraries - azure_logger = logging.getLogger("azure") - azure_logger.setLevel(logging.DEBUG) - console_handler = logging.StreamHandler(stream=sys.stdout) - console_handler.addFilter(_AuthSecretsFilter()) - azure_logger.addHandler(console_handler) - # Exclude detailed logs for network calls associated with getting Entra ID token. - logging.getLogger("azure.identity").setLevel(logging.ERROR) - # Make sure regular (redacted) detailed azure.core logs are not shown, as we are about to - # turn on non-redacted logs by passing 'logging_enable=True' to the client constructor - # (which are implemented as a separate logging policy) - logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.ERROR) - - kwargs.setdefault("logging_enable", self._console_logging_enabled) - - self._kwargs = kwargs.copy() - self._custom_user_agent = self._kwargs.get("user_agent", None) - - super().__init__(endpoint=endpoint, credential=credential, allow_preview=allow_preview, **kwargs) - - self.telemetry = TelemetryOperations(self) # type: ignore - - @distributed_trace - def get_openai_client(self, **kwargs: Any) -> OpenAI: - """Get an authenticated OpenAI client from the `openai` package. - - Keyword arguments are passed to the OpenAI client constructor. - - The OpenAI client constructor is called with: - * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. - Can be overridden by passing ``base_url`` as a keyword argument. - * If :class:`~azure.ai.projects.AIProjectClient` was constructed with a bearer token, ``api_key`` is set - to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient - constructor, with scope ``https://ai.azure.com/.default``. - Can be overridden by passing ``api_key`` as a keyword argument. - * If :class:`~azure.ai.projects.AIProjectClient` was constructed with ``api-key``, it is passed to the - OpenAI constructor as is. - Can be overridden by passing ``api_key`` as a keyword argument. - - .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. - - :return: An authenticated OpenAI client - :rtype: ~openai.OpenAI - - :raises ~azure.core.exceptions.HttpResponseError: - """ - - kwargs = kwargs.copy() if kwargs else {} - - # Allow caller to override base_url - if "base_url" in kwargs: - base_url = kwargs.pop("base_url") - else: - base_url = self._config.endpoint.rstrip("/") + "/openai/v1" # pylint: disable=protected-access - - 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, - ) - - # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor - if "api_key" in kwargs: - api_key = kwargs.pop("api_key") - else: - api_key = ( - self._config.credential.key # pylint: disable=protected-access - if isinstance(self._config.credential, AzureKeyCredential) - else get_bearer_token_provider( - self._config.credential, # pylint: disable=protected-access - "https://ai.azure.com/.default", - ) - ) - - if "http_client" in kwargs: - http_client = kwargs.pop("http_client") - elif self._console_logging_enabled: - http_client = httpx.Client(transport=OpenAILoggingTransport()) - else: - http_client = None - - default_headers = dict[str, str](kwargs.pop("default_headers", None) or {}) - - openai_custom_user_agent = default_headers.get("User-Agent", None) - - def _create_openai_client(**kwargs) -> OpenAI: - return OpenAI( - api_key=api_key, - base_url=base_url, - http_client=http_client, - **kwargs, - ) - - dummy_client = _create_openai_client() - - openai_default_user_agent = dummy_client.user_agent - - if openai_custom_user_agent: - final_user_agent = openai_custom_user_agent - else: - final_user_agent = ( - "-".join(ua for ua in [self._custom_user_agent, "AIProjectClient"] if ua) - + " " - + openai_default_user_agent - ) - - default_headers["User-Agent"] = final_user_agent - - client = _create_openai_client(default_headers=default_headers, **kwargs) - - return client - - -class _AuthSecretsFilter(logging.Filter): - """Redact bearer tokens and api-key values in azure.core log messages before they are emitted to console.""" - - _AUTH_HEADER_DICT_PATTERN = re.compile( - r"(?i)(['\"]authorization['\"]\ *:\ *['\"])bearer\s+[^'\"]+(['\"])", - ) - - _API_KEY_HEADER_DICT_PATTERN = re.compile( - r"(?i)(['\"]api-key['\"]\ *:\ *['\"])[^'\"]+(['\"])", - ) - - def filter(self, record: logging.LogRecord) -> bool: - rendered = record.getMessage() - redacted = self._AUTH_HEADER_DICT_PATTERN.sub(r"\1Bearer \2", rendered) - redacted = self._API_KEY_HEADER_DICT_PATTERN.sub(r"\1\2", redacted) - if redacted != rendered: - # Replace the pre-formatted content so handlers emit sanitized output. - record.msg = redacted - record.args = () - return True - - -class OpenAILoggingTransport(httpx.HTTPTransport): - """Custom HTTP transport that logs OpenAI API requests and responses to the console. - - This transport wraps httpx.HTTPTransport to intercept all HTTP traffic and print - detailed request/response information for debugging purposes. It automatically - redacts sensitive authorization headers and handles various content types including - multipart form data (file uploads). - - Used internally by AIProjectClient when console logging is enabled via the - AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable. - """ - - def _sanitize_auth_header(self, headers) -> None: - """Sanitize authorization and api-key headers by redacting sensitive information. - - :param headers: Dictionary of HTTP headers to sanitize - :type headers: dict - """ - - if "authorization" in headers: - auth_value = headers["authorization"] - if len(auth_value) >= 7: - headers["authorization"] = auth_value[:7] + "" - else: - headers["authorization"] = "" - - def handle_request(self, request: httpx.Request) -> httpx.Response: - """ - Log HTTP request and response details to console, in a nicely formatted way, - for OpenAI / Azure OpenAI clients. - - :param request: The HTTP request to handle and log - :type request: httpx.Request - - :return: The HTTP response received - :rtype: httpx.Response - """ - - print(f"\n==> Request:\n{request.method} {request.url}") - headers = dict(request.headers) - self._sanitize_auth_header(headers) - print("Headers:") - for key, value in sorted(headers.items()): - print(f" {key}: {value}") - - self._log_request_body(request) - - response = super().handle_request(request) - - print(f"\n<== Response:\n{response.status_code} {response.reason_phrase}") - print("Headers:") - for key, value in sorted(dict(response.headers).items()): - print(f" {key}: {value}") - - content = response.read() - if content is None or content == b"": - print("Body: [No content]") - else: - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - print("\n") - - return response - - def _log_request_body(self, request: httpx.Request) -> None: - """Log request body content safely, handling binary data and streaming content. - - :param request: The HTTP request object containing the body to log - :type request: httpx.Request - """ - - # Check content-type header to identify file uploads - content_type = request.headers.get("content-type", "").lower() - if "multipart/form-data" in content_type: - print("Body: [Multipart form data - file upload, not logged]") - return - - # Safely check if content exists without accessing it - if not hasattr(request, "content"): - print("Body: [No content attribute]") - return - - # Very careful content access - wrap in try-catch immediately - try: - content = request.content - except Exception as access_error: # pylint: disable=broad-exception-caught - print(f"Body: [Cannot access content: {access_error}]") - return - - if content is None or content == b"": - print("Body: [No content]") - return - - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - - -__all__: List[str] = [ - "AIProjectClient", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py deleted file mode 100644 index f5af3a4eb8a2..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_validation.py +++ /dev/null @@ -1,66 +0,0 @@ -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# Code generated by Microsoft (R) Python Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is regenerated. -# -------------------------------------------------------------------------- -import functools - - -def api_version_validation(**kwargs): - params_added_on = kwargs.pop("params_added_on", {}) - method_added_on = kwargs.pop("method_added_on", "") - api_versions_list = kwargs.pop("api_versions_list", []) - - def _index_with_default(value: str, default: int = -1) -> int: - """Get the index of value in lst, or return default if not found. - - :param value: The value to search for in the api_versions_list. - :type value: str - :param default: The default value to return if the value is not found. - :type default: int - :return: The index of the value in the list, or the default value if not found. - :rtype: int - """ - try: - return api_versions_list.index(value) - except ValueError: - return default - - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - try: - # this assumes the client has an _api_version attribute - client = args[0] - client_api_version = client._config.api_version # pylint: disable=protected-access - except AttributeError: - return func(*args, **kwargs) - - if _index_with_default(method_added_on) > _index_with_default(client_api_version): - raise ValueError( - f"'{func.__name__}' is not available in API version " - f"{client_api_version}. Pass service API version {method_added_on} or newer to your client." - ) - - unsupported = { - parameter: api_version - for api_version, parameters in params_added_on.items() - for parameter in parameters - if parameter in kwargs and _index_with_default(api_version) > _index_with_default(client_api_version) - } - if unsupported: - raise ValueError( - "".join( - [ - f"'{param}' is not available in API version {client_api_version}. " - f"Use service API version {version} or newer.\n" - for param, version in unsupported.items() - ] - ) - ) - return func(*args, **kwargs) - - return wrapper - - return decorator diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py index 85d7b3924370..ccb75164d3bc 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_version.py @@ -6,4 +6,4 @@ # Changes may cause incorrect behavior and will be lost if the code is regenerated. # -------------------------------------------------------------------------- -VERSION = "2.0.2" +VERSION = "2.1.0" 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 b79f5c6a93e8..87676c65a8f0 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 @@ -1,300 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -import os -import logging -from typing import List, Any, Union -import httpx # pylint: disable=networking-import-outside-azure-core-transport -from openai import AsyncOpenAI -from azure.core.credentials import AzureKeyCredential -from azure.core.tracing.decorator import distributed_trace -from azure.core.credentials_async import AsyncTokenCredential -from azure.identity.aio import get_bearer_token_provider -from .._patch import _AuthSecretsFilter -from ._client import AIProjectClient as AIProjectClientGenerated -from .operations import TelemetryOperations -logger = logging.getLogger(__name__) - - -class AIProjectClient(AIProjectClientGenerated): # pylint: disable=too-many-instance-attributes - """AIProjectClient. - - :ivar beta: BetaOperations operations - :vartype beta: azure.ai.projects.aio.operations.BetaOperations - :ivar agents: AgentsOperations operations - :vartype agents: azure.ai.projects.aio.operations.AgentsOperations - :ivar evaluation_rules: EvaluationRulesOperations operations - :vartype evaluation_rules: azure.ai.projects.aio.operations.EvaluationRulesOperations - :ivar connections: ConnectionsOperations operations - :vartype connections: azure.ai.projects.aio.operations.ConnectionsOperations - :ivar datasets: DatasetsOperations operations - :vartype datasets: azure.ai.projects.aio.operations.DatasetsOperations - :ivar deployments: DeploymentsOperations operations - :vartype deployments: azure.ai.projects.aio.operations.DeploymentsOperations - :ivar indexes: IndexesOperations operations - :vartype indexes: azure.ai.projects.aio.operations.IndexesOperations - :param endpoint: Foundry Project endpoint in the form - "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you - only have one Project in your Foundry Hub, or to target the default Project in your Hub, use - the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". - Required. - :type endpoint: str - :param credential: Credential used to authenticate requests to the service. Is either a key - credential type or a token credential type. Required. - :type credential: ~azure.core.credentials.AzureKeyCredential or - ~azure.core.credentials_async.AsyncTokenCredential - :param allow_preview: Whether to enable preview features. Optional, default is False. - Set this to True to create a Hosted Agent (using :class:`~azure.ai.projects.models.HostedAgentDefinition`) - or a Workflow Agent (using :class:`~azure.ai.projects.models.WorkflowAgentDefinition`). - Set this to True to use human evaluation rule action (class :class:`~azure.ai.projects.models.HumanEvaluationPreviewRuleAction`). - Methods on the `.beta` sub-client (class :class:`~azure.ai.projects.aio.operations.BetaOperations`) - are all in preview, but do not require setting `allow_preview=True` since it's implied by the sub-client name. - When preview features are enabled, the client libraries sends the HTTP request header `Foundry-Features` - with the appropriate value in all relevant calls to the service. - :type allow_preview: bool - :keyword api_version: The API version to use for this operation. Known values are "v1". Default - value is "v1". Note that overriding this default value may result in unsupported behavior. - :paramtype api_version: str - """ - - def __init__( - self, - endpoint: str, - credential: Union[AzureKeyCredential, "AsyncTokenCredential"], - *, - allow_preview: bool = False, - **kwargs: Any, - ) -> None: - - self._console_logging_enabled: bool = ( - os.environ.get("AZURE_AI_PROJECTS_CONSOLE_LOGGING", "false").lower() == "true" - ) - - if self._console_logging_enabled: - import sys - - # Enable detailed console logs across Azure libraries - azure_logger = logging.getLogger("azure") - azure_logger.setLevel(logging.DEBUG) - console_handler = logging.StreamHandler(stream=sys.stdout) - console_handler.addFilter(_AuthSecretsFilter()) - azure_logger.addHandler(console_handler) - # Exclude detailed logs for network calls associated with getting Entra ID token. - logging.getLogger("azure.identity").setLevel(logging.ERROR) - # Make sure regular (redacted) detailed azure.core logs are not shown, as we are about to - # turn on non-redacted logs by passing 'logging_enable=True' to the client constructor - # (which are implemented as a separate logging policy) - logging.getLogger("azure.core.pipeline.policies.http_logging_policy").setLevel(logging.ERROR) - - kwargs.setdefault("logging_enable", self._console_logging_enabled) - - self._kwargs = kwargs.copy() - self._custom_user_agent = self._kwargs.get("user_agent", None) - - super().__init__(endpoint=endpoint, credential=credential, allow_preview=allow_preview, **kwargs) - - self.telemetry = TelemetryOperations(self) # type: ignore - - @distributed_trace - def get_openai_client(self, **kwargs: Any) -> AsyncOpenAI: - """Get an authenticated AsyncOpenAI client from the `openai` package. - - Keyword arguments are passed to the AsyncOpenAI client constructor. - - The AsyncOpenAI client constructor is called with: - * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai/v1" appended. - Can be overridden by passing ``base_url`` as a keyword argument. - * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with a bearer token, ``api_key`` is set - to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient - constructor, with scope ``https://ai.azure.com/.default``. - Can be overridden by passing ``api_key`` as a keyword argument. - * If :class:`~azure.ai.projects.aio.AIProjectClient` was constructed with ``api-key``, it is passed to the - OpenAI constructor as is. - Can be overridden by passing ``api_key`` as a keyword argument. - - .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. - - :return: An authenticated AsyncOpenAI client - :rtype: ~openai.AsyncOpenAI - - :raises ~azure.core.exceptions.HttpResponseError: - """ - - kwargs = kwargs.copy() if kwargs else {} - - # Allow caller to override base_url - if "base_url" in kwargs: - base_url = kwargs.pop("base_url") - else: - base_url = self._config.endpoint.rstrip("/") + "/openai/v1" # pylint: disable=protected-access - - 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, - ) - - # Allow caller to override api_key, otherwise use api-key or token provider given during AIProjectClient constructor - if "api_key" in kwargs: - api_key = kwargs.pop("api_key") - else: - api_key = ( - self._config.credential.key # pylint: disable=protected-access - if isinstance(self._config.credential, AzureKeyCredential) - else get_bearer_token_provider( - self._config.credential, # pylint: disable=protected-access - "https://ai.azure.com/.default", - ) - ) - - if "http_client" in kwargs: - http_client = kwargs.pop("http_client") - elif self._console_logging_enabled: - http_client = httpx.AsyncClient(transport=OpenAILoggingTransport()) - else: - http_client = None - - default_headers = dict[str, str](kwargs.pop("default_headers", None) or {}) - - openai_custom_user_agent = default_headers.get("User-Agent", None) - - def _create_openai_client(**kwargs) -> AsyncOpenAI: - return AsyncOpenAI( - api_key=api_key, - base_url=base_url, - http_client=http_client, - **kwargs, - ) - - dummy_client = _create_openai_client() - - openai_default_user_agent = dummy_client.user_agent - - if openai_custom_user_agent: - final_user_agent = openai_custom_user_agent - else: - final_user_agent = ( - "-".join(ua for ua in [self._custom_user_agent, "AIProjectClient"] if ua) - + " " - + openai_default_user_agent - ) - - default_headers["User-Agent"] = final_user_agent - - client = _create_openai_client(default_headers=default_headers, **kwargs) - - return client - - -class OpenAILoggingTransport(httpx.AsyncHTTPTransport): - """Custom HTTP async transport that logs OpenAI API requests and responses to the console. - - This transport wraps httpx.AsyncHTTPTransport to intercept all HTTP traffic and print - detailed request/response information for debugging purposes. It automatically - redacts sensitive authorization headers and handles various content types including - multipart form data (file uploads). - - Used internally by AIProjectClient when console logging is enabled via the - AZURE_AI_PROJECTS_CONSOLE_LOGGING environment variable. - """ - - def _sanitize_auth_header(self, headers): - """Sanitize authorization and api-key headers by redacting sensitive information. - - :param headers: Dictionary of HTTP headers to sanitize - :type headers: dict - """ - - if "authorization" in headers: - auth_value = headers["authorization"] - if len(auth_value) >= 7: - headers["authorization"] = auth_value[:7] + "" - else: - headers["authorization"] = "" - - async def handle_async_request(self, request: httpx.Request) -> httpx.Response: - """ - Log HTTP request and response details to console, in a nicely formatted way, - for OpenAI / Azure OpenAI clients. - - :param request: The HTTP request to handle and log - :type request: httpx.Request - - :return: The HTTP response received - :rtype: httpx.Response - """ - - print(f"\n==> Request:\n{request.method} {request.url}") - headers = dict(request.headers) - self._sanitize_auth_header(headers) - print("Headers:") - for key, value in sorted(headers.items()): - print(f" {key}: {value}") - - self._log_request_body(request) - - response = await super().handle_async_request(request) - - print(f"\n<== Response:\n{response.status_code} {response.reason_phrase}") - print("Headers:") - for key, value in sorted(dict(response.headers).items()): - print(f" {key}: {value}") - - content = await response.aread() - if content is None or content == b"": - print("Body: [No content]") - else: - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - print("\n") - - return response - - def _log_request_body(self, request: httpx.Request) -> None: - """Log request body content safely, handling binary data and streaming content. - - :param request: The HTTP request object containing the body to log - :type request: httpx.Request - """ - - # Check content-type header to identify file uploads - content_type = request.headers.get("content-type", "").lower() - if "multipart/form-data" in content_type: - print("Body: [Multipart form data - file upload, not logged]") - return - - # Safely check if content exists without accessing it - if not hasattr(request, "content"): - print("Body: [No content attribute]") - return - - # Very careful content access - wrap in try-catch immediately - try: - content = request.content - except Exception as access_error: # pylint: disable=broad-exception-caught - print(f"Body: [Cannot access content: {access_error}]") - return - - if content is None or content == b"": - print("Body: [No content]") - return - - try: - print(f"Body:\n {content.decode('utf-8')}") - except Exception: # pylint: disable=broad-exception-caught - print(f"Body (raw):\n {content!r}") - - -__all__: List[str] = ["AIProjectClient"] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py index 50659948669c..7bffabfd89e1 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_operations.py @@ -3097,10 +3097,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3530,10 +3527,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -3633,10 +3627,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -4693,10 +4684,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5364,7 +5352,7 @@ async def _search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -5450,7 +5438,7 @@ async def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, @@ -5898,10 +5886,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6247,10 +6232,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6550,10 +6532,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py index c448e46abae8..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch.py @@ -1,87 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import Any, List -from ._patch_agents_async import AgentsOperations -from ._patch_datasets_async import DatasetsOperations -from ._patch_evaluation_rules_async import EvaluationRulesOperations -from ._patch_evaluators_async import BetaEvaluatorsOperations -from ._patch_telemetry_async import TelemetryOperations -from ._patch_connections_async import ConnectionsOperations -from ._patch_memories_async import BetaMemoryStoresOperations -from ...operations._patch import _BETA_OPERATION_FEATURE_HEADERS, _OperationMethodHeaderProxy -from ._operations import ( - BetaEvaluationTaxonomiesOperations, - BetaInsightsOperations, - BetaOperations as GeneratedBetaOperations, - BetaRedTeamsOperations, - BetaSchedulesOperations, - BetaToolsetsOperations, -) - -class BetaOperations(GeneratedBetaOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`beta` attribute. - """ - - evaluation_taxonomies: BetaEvaluationTaxonomiesOperations - """:class:`~azure.ai.projects.aio.operations.BetaEvaluationTaxonomiesOperations` operations""" - evaluators: BetaEvaluatorsOperations - """:class:`~azure.ai.projects.aio.operations.BetaEvaluatorsOperations` operations""" - insights: BetaInsightsOperations - """:class:`~azure.ai.projects.aio.operations.BetaInsightsOperations` operations""" - memory_stores: BetaMemoryStoresOperations - """:class:`~azure.ai.projects.aio.operations.BetaMemoryStoresOperations` operations""" - red_teams: BetaRedTeamsOperations - """:class:`~azure.ai.projects.aio.operations.BetaRedTeamsOperations` operations""" - schedules: BetaSchedulesOperations - """:class:`~azure.ai.projects.aio.operations.BetaSchedulesOperations` operations""" - toolsets: BetaToolsetsOperations - """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - # Replace with patched class that includes upload() - self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) - # Replace with patched class that includes begin_update_memories - self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) - - for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): - setattr( - self, - property_name, - _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), - ) - - -__all__: List[str] = [ - "AgentsOperations", - "BetaEvaluationTaxonomiesOperations", - "BetaEvaluatorsOperations", - "BetaInsightsOperations", - "BetaMemoryStoresOperations", - "BetaOperations", - "BetaRedTeamsOperations", - "BetaSchedulesOperations", - "BetaToolsetsOperations", - "ConnectionsOperations", - "DatasetsOperations", - "EvaluationRulesOperations", - "TelemetryOperations", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py deleted file mode 100644 index f2a6dbc62624..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_agents_async.py +++ /dev/null @@ -1,192 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, IO, overload -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator_async import distributed_trace_async -from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset -from ... import models as _models -from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive -from ...operations._patch_agents import ( - _AGENT_OPERATION_FEATURE_HEADERS, - _PREVIEW_FEATURE_REQUIRED_CODE, - _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE, -) - - -class AgentsOperations(GeneratedAgentsOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`agents` attribute. - """ - - @overload - async def create_version( - self, - agent_name: str, - *, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_version( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_version( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace_async - async def create_version( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS - kwargs["headers"] = headers - - try: - return await super().create_version( - agent_name, - body, - definition=definition, - metadata=metadata, - description=description, - **kwargs, - ) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py deleted file mode 100644 index f32515aca2fe..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_connections_async.py +++ /dev/null @@ -1,74 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Any, Optional, Union -from azure.core.tracing.decorator_async import distributed_trace_async - -from ._operations import ConnectionsOperations as ConnectionsOperationsGenerated -from ...models._models import Connection -from ...models._enums import ConnectionType - - -class ConnectionsOperations(ConnectionsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`connections` attribute. - """ - - @distributed_trace_async - async def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: - """Get a connection by name. - - :param name: The name of the resource. Required. - :type name: str - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ~azure.core.exceptions.HttpResponseError: - """ - - if include_credentials: - connection = await super()._get_with_credentials(name, **kwargs) - if connection.type == ConnectionType.CUSTOM: - # Why do we do this? See comment in the sync version of this code (file _patch_connections.py). - setattr( - connection.credentials, - "credential_keys", - {k: v for k, v in connection.credentials.as_dict().items() if k != "type"}, - ) - - return connection - - return await super()._get(name, **kwargs) - - @distributed_trace_async - async def get_default( - self, connection_type: Union[str, ConnectionType], *, include_credentials: Optional[bool] = False, **kwargs: Any - ) -> Connection: - """Get the default connection for a given connection type. - - :param connection_type: The type of the connection. Required. - :type connection_type: str or ~azure.ai.projects.models.ConnectionType - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ValueError: If no default connection is found for the given type. - :raises ~azure.core.exceptions.HttpResponseError: - """ - connections = super().list(connection_type=connection_type, default_connection=True, **kwargs) - async for connection in connections: - return await self.get(connection.name, include_credentials=include_credentials, **kwargs) - raise ValueError(f"No default connection found for type: {connection_type}.") diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py deleted file mode 100644 index dc7095c827ea..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_datasets_async.py +++ /dev/null @@ -1,223 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import re -import logging -from typing import Any, Tuple, Optional -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob.aio import ContainerClient -from azure.core.tracing.decorator_async import distributed_trace_async - -from ._operations import DatasetsOperations as DatasetsOperationsGenerated -from ...models._models import ( - FileDatasetVersion, - FolderDatasetVersion, - PendingUploadRequest, - PendingUploadResponse, - PendingUploadType, -) - -logger = logging.getLogger(__name__) - - -class DatasetsOperations(DatasetsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`datasets` attribute. - """ - - # Internal helper method to create a new dataset and return a ContainerClient from azure-storage-blob package, - # to the dataset's blob storage. - async def _create_dataset_and_get_its_container_client( - self, - name: str, - input_version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str]: - - pending_upload_response: PendingUploadResponse = await self.pending_upload( - name=name, - version=input_version, - pending_upload_request=PendingUploadRequest( - pending_upload_type=PendingUploadType.BLOB_REFERENCE, - connection_name=connection_name, - ), - ) - output_version: str = input_version - - if not pending_upload_response.blob_reference: - raise ValueError("Blob reference is not present") - if not pending_upload_response.blob_reference.credential: - raise ValueError("SAS credential are not present") - if not pending_upload_response.blob_reference.credential.sas_uri: - raise ValueError("SAS URI is missing or empty") - - # For overview on Blob storage SDK in Python see: - # https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python - # https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-python - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-from-container-url - return ( - ContainerClient.from_container_url( - container_url=pending_upload_response.blob_reference.credential.sas_uri, # Of the form: "https://.blob.core.windows.net/?" - ), - output_version, - ) - - @distributed_trace_async - async def upload_file( - self, *, name: str, version: str, file_path: str, connection_name: Optional[str] = None, **kwargs: Any - ) -> FileDatasetVersion: - """Upload file to a blob storage, and create a dataset that references this file. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload the file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword file_path: The file name (including optional path) to be uploaded. Required. - :paramtype file_path: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FileDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - - pathlib_file_path = Path(file_path) - if not pathlib_file_path.exists(): - raise ValueError(f"The provided file `{file_path}` does not exist.") - if pathlib_file_path.is_dir(): - raise ValueError("The provided file is actually a folder. Use method `upload_folder` instead") - - container_client, output_version = await self._create_dataset_and_get_its_container_client( - name=name, - input_version=version, - connection_name=connection_name, - ) - - async with container_client: - - with open(file=file_path, mode="rb") as data: # TODO: What is the best async options for file reading? - - blob_name = pathlib_file_path.name # Extract the file name from the path. - logger.debug( - "[upload_file] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-upload-blob - async with await container_client.upload_blob(name=blob_name, data=data, **kwargs) as blob_client: - logger.debug("[upload_file] Done uploading") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net//" - data_uri = urlsplit(blob_client.url)._replace(query="").geturl() - - dataset_version = await self.create_or_update( - name=name, - version=output_version, - dataset_version=FileDatasetVersion( - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#azure-storage-blob-blobclient-url - # Per above doc the ".url" contains SAS token... should this be stripped away? - data_uri=data_uri, - ), - ) - - return dataset_version # type: ignore - - @distributed_trace_async - async def upload_folder( - self, - *, - name: str, - version: str, - folder: str, - connection_name: Optional[str] = None, - file_pattern: Optional[re.Pattern] = None, - **kwargs: Any, - ) -> FolderDatasetVersion: - """Upload all files in a folder and its sub folders to a blob storage, while maintaining - relative paths, and create a dataset that references this folder. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload each file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword folder: The folder name (including optional path) to be uploaded. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :keyword file_pattern: A regex pattern to filter files to be uploaded. Only files matching the pattern - will be uploaded. Optional. - :paramtype file_pattern: re.Pattern - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FolderDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not Path(path_folder).exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if Path(path_folder).is_file(): - raise ValueError("The provided folder is actually a file. Use method `upload_file` instead.") - - container_client, output_version = await self._create_dataset_and_get_its_container_client( - name=name, input_version=version, connection_name=connection_name - ) - - async with container_client: - - # Recursively traverse all files in the folder - files_uploaded: bool = False - for root, _, files in os.walk(folder): - for file in files: - if file_pattern and not file_pattern.search(file): - continue # Skip files that do not match the pattern - file_path = os.path.join(root, file) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") # Ensure correct format for Azure - logger.debug( - "[upload_folder] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - with open(file=file_path, mode="rb") as data: # Open the file for reading in binary mode - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-upload-blob - await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - logger.debug("[upload_folder] Done uploading file") - files_uploaded = True - logger.debug("[upload_folder] Done uploaded.") - - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net/" - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.aio.containerclient?view=azure-python#azure-storage-blob-aio-containerclient-url - data_uri = urlsplit(container_client.url)._replace(query="").geturl() - - dataset_version = await self.create_or_update( - name=name, - version=output_version, - dataset_version=FolderDatasetVersion(data_uri=data_uri), - ) - - return dataset_version # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py deleted file mode 100644 index a296493c469b..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluation_rules_async.py +++ /dev/null @@ -1,129 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Any, IO, overload -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator_async import distributed_trace_async -from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON -from ... import models as _models -from ...operations._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE -from ...models._enums import _FoundryFeaturesOptInKeys -from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive - - -class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`evaluation_rules` attribute. - """ - - @overload - async def create_or_update( - self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_or_update( - self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - async def create_or_update( - self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace_async - async def create_or_update( - self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Is one of the following types: - EvaluationRule, JSON, IO[bytes] Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - } - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - kwargs["headers"] = headers - - try: - return await super().create_or_update(id, evaluation_rule, **kwargs) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py deleted file mode 100644 index c6c366fd5956..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_evaluators_async.py +++ /dev/null @@ -1,258 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import logging -from typing import Any, Final, IO, Tuple, Optional, Union -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob.aio import ContainerClient -from azure.core.tracing.decorator_async import distributed_trace_async -from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ...models._enums import _FoundryFeaturesOptInKeys -from ...models._patch import _FOUNDRY_FEATURES_HEADER_NAME -from ...models._models import ( - CodeBasedEvaluatorDefinition, - EvaluatorVersion, -) - -logger = logging.getLogger(__name__) - -_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - - -class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`beta.evaluators` attribute. - """ - - @staticmethod - async def _upload_folder_to_blob( - container_client: ContainerClient, - folder: str, - **kwargs: Any, - ) -> None: - """Walk *folder* and upload every eligible file to the blob container. - - Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` - directories and ``.pyc`` / ``.pyo`` files. - - :param container_client: The blob container client to upload files to. - :type container_client: ~azure.storage.blob.ContainerClient - :param folder: Path to the local folder containing files to upload. - :type folder: str - :raises ValueError: If the folder contains no uploadable files. - :raises HttpResponseError: Re-raised with a friendlier message on - ``AuthorizationPermissionMismatch``. - """ - skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} - skip_extensions = {".pyc", ".pyo"} - files_uploaded = False - - for root, dirs, files in os.walk(folder): - dirs[:] = [d for d in dirs if d not in skip_dirs] - for file_name in files: - if any(file_name.endswith(ext) for ext in skip_extensions): - continue - file_path = os.path.join(root, file_name) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") - logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) - with open(file=file_path, mode="rb") as data: - try: - await container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - except HttpResponseError as e: - if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": - storage_account = urlsplit(container_client.url).hostname - raise HttpResponseError( - message=( - f"Failed to upload file '{blob_name}' to blob storage: " - f"permission denied. Ensure the identity that signed the SAS token " - f"has the 'Storage Blob Data Contributor' role on the storage account " - f"'{storage_account}'. " - f"Original error: {e.message}" - ), - response=e.response, - ) from e - raise - logger.debug("[upload] Done uploading file") - files_uploaded = True - - logger.debug("[upload] Done uploading all files.") - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - @staticmethod - def _set_blob_uri( - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - blob_uri: str, - ) -> None: - """Set ``blob_uri`` on the evaluator version's definition. - - :param evaluator_version: The evaluator version object to update. - :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] - :param blob_uri: The blob URI to set on the definition. - :type blob_uri: str - """ - if isinstance(evaluator_version, dict): - definition = evaluator_version.get("definition", {}) - if isinstance(definition, dict): - definition["blob_uri"] = blob_uri - elif isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - elif isinstance(evaluator_version, EvaluatorVersion): - definition = evaluator_version.definition - if isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - - async def _start_pending_upload_and_get_container_client( - self, - name: str, - version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str, str]: - """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. - - :param name: The evaluator name. - :type name: str - :param version: The evaluator version. - :type version: str - :param connection_name: Optional storage account connection name. - :type connection_name: Optional[str] - :return: A tuple of (ContainerClient, version, blob_uri). - :rtype: Tuple[ContainerClient, str, str] - """ - - request_body: dict = {} - if connection_name: - request_body["connectionName"] = connection_name - - pending_upload_response = await self.pending_upload( - name=name, - version=version, - pending_upload_request=request_body, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - # The service returns blobReferenceForConsumption - blob_ref = pending_upload_response.get("blobReferenceForConsumption") - if not blob_ref: - raise ValueError("Blob reference is not present in the pending upload response") - - credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None - if not credential: - raise ValueError("SAS credential is not present in the pending upload response") - - sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None - if not sas_uri: - raise ValueError("SAS URI is missing or empty in the pending upload response") - - blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None - if not blob_uri: - raise ValueError("Blob URI is missing or empty in the pending upload response") - - return ( - ContainerClient.from_container_url(container_url=sas_uri), - version, - blob_uri, - ) - - async def _get_next_version(self, name: str) -> str: - """Get the next version number for an evaluator by fetching existing versions. - - :param name: The evaluator name. - :type name: str - :return: The next version number as a string. - :rtype: str - """ - try: - versions = [] - async for v in self.list_versions( - name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} - ): - versions.append(v) - if versions: - numeric_versions = [] - for v in versions: - ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) - if ver and ver.isdigit(): - numeric_versions.append(int(ver)) - if numeric_versions: - return str(max(numeric_versions) + 1) - return "1" - except ResourceNotFoundError: - return "1" - - @distributed_trace_async - async def upload( - self, - name: str, - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - *, - folder: str, - connection_name: Optional[str] = None, - **kwargs: Any, - ) -> EvaluatorVersion: - """Upload all files in a folder to blob storage and create a code-based evaluator version - that references the uploaded code. - - This method calls startPendingUpload to get a SAS URI, uploads files from the folder - to blob storage, then creates an evaluator version referencing the uploaded blob. - - The version is automatically determined by incrementing the latest existing version. - - :param name: The name of the evaluator. Required. - :type name: str - :param evaluator_version: The evaluator version definition. This is the same object accepted - by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, - IO[bytes]. Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :keyword folder: Path to the folder containing the evaluator Python code. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection where the files - should be uploaded. If not specified, the default Azure Storage Account connection will be - used. Optional. - :paramtype connection_name: str - :return: The created evaluator version. - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not path_folder.exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if path_folder.is_file(): - raise ValueError("The provided path is a file, not a folder.") - - version = await self._get_next_version(name) - logger.info("[upload] Auto-resolved version to '%s'.", version) - - # Get SAS URI via startPendingUpload - container_client, _, blob_uri = await self._start_pending_upload_and_get_container_client( - name=name, - version=version, - connection_name=connection_name, - ) - - async with container_client: - await self._upload_folder_to_blob(container_client, folder, **kwargs) - self._set_blob_uri(evaluator_version, blob_uri) - - result = await self.create_version( - name=name, - evaluator_version=evaluator_version, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py deleted file mode 100644 index 856d3433a6a7..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_memories_async.py +++ /dev/null @@ -1,379 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, overload, IO, cast -from openai.types.responses import ResponseInputParam -from azure.core.tracing.decorator_async import distributed_trace_async -from azure.core.polling import AsyncNoPolling -from azure.core.utils import case_insensitive_dict -from ... import models as _models -from ...models import ( - MemoryStoreOperationUsage, - ResponseUsageInputTokensDetails, - ResponseUsageOutputTokensDetails, - MemoryStoreUpdateCompletedResult, - AsyncUpdateMemoriesLROPoller, -) -from ...models._patch import ( - _AsyncUpdateMemoriesLROPollingMethod, - _FOUNDRY_FEATURES_HEADER_NAME, - _BETA_OPERATION_FEATURE_HEADERS, -) -from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations -from ...operations._patch_memories import _serialize_memory_input_items -from ..._validation import api_version_validation -from ..._utils.model_base import _deserialize - - -class BetaMemoryStoresOperations(GenerateBetaMemoryStoresOperations): - - @overload - async def search_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def search_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def search_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - async def search_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - return await super()._search_memories( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_search_id=previous_search_id, - options=options, - **kwargs, - ) - - @overload - async def begin_update_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def begin_update_memories( - self, - name: str, - body: JSON, - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - async def begin_update_memories( - self, - name: str, - body: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of AsyncUpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace_async - @api_version_validation( - method_added_on="v1", - params_added_on={"v1": ["api_version", "name", "content_type", "accept"]}, - api_versions_list=["v1"], - ) - async def begin_update_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> AsyncUpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :return: An instance of AsyncLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[_models.MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling = kwargs.pop("polling", True) - if not isinstance(polling, bool): - raise TypeError("polling must be of type bool.") - lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) - cont_token: Optional[str] = kwargs.pop("continuation_token", None) - if cont_token is None: - raw_result = await self._update_memories_initial( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_update_id=previous_update_id, - update_delay=update_delay, - content_type=content_type, - cls=lambda x, y, z: x, - headers=_headers, - params=_params, - **kwargs, - ) - await raw_result.http_response.read() # type: ignore - - raw_result.http_response.status_code = 202 # type: ignore - raw_result.http_response.headers["Operation-Location"] = ( # type: ignore - f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=v1" # type: ignore - ) - - kwargs.pop("error_map", None) - - def get_long_running_output(pipeline_response): - response_headers = {} - response = pipeline_response.http_response - response_headers["Operation-Location"] = self._deserialize( - "str", response.headers.get("Operation-Location") - ) - - deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) - if deserialized is None: - usage = MemoryStoreOperationUsage( - embedding_tokens=0, - input_tokens=0, - input_tokens_details=ResponseUsageInputTokensDetails(cached_tokens=0), - output_tokens=0, - output_tokens_details=ResponseUsageOutputTokensDetails(reasoning_tokens=0), - total_tokens=0, - ) - deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - return deserialized - - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - - if polling: - polling_method: _AsyncUpdateMemoriesLROPollingMethod = _AsyncUpdateMemoriesLROPollingMethod( - lro_delay, - path_format_arguments=path_format_arguments, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, - **kwargs, - ) - else: - polling_method = cast(_AsyncUpdateMemoriesLROPollingMethod, AsyncNoPolling()) - - if cont_token: - return AsyncUpdateMemoriesLROPoller.from_continuation_token( - polling_method=polling_method, - continuation_token=cont_token, - client=self._client, - deserialization_callback=get_long_running_output, - ) - - return AsyncUpdateMemoriesLROPoller( - self._client, - raw_result, # type: ignore[possibly-undefined] - get_long_running_output, - polling_method, # pylint: disable=possibly-used-before-assignment - ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py deleted file mode 100644 index 7c50fb95ca96..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/operations/_patch_telemetry_async.py +++ /dev/null @@ -1,75 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Optional, AsyncIterable -from azure.core.exceptions import ResourceNotFoundError -from azure.core.tracing.decorator_async import distributed_trace_async - -from ...models._models import ( - Connection, - ApiKeyCredentials, -) -from ...models._enums import ConnectionType - - -class TelemetryOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.aio.AIProjectClient`'s - :attr:`telemetry` attribute. - """ - - _connection_string: Optional[str] = None - - def __init__(self, outer_instance: "azure.ai.projects.aio.AIProjectClient") -> None: # type: ignore[name-defined] - self._outer_instance = outer_instance - - @distributed_trace_async - async def get_application_insights_connection_string(self) -> str: # pylint: disable=name-too-long - """Get the Application Insights connection string associated with the Project's Application Insights resource. - - :return: The Application Insights connection string if a the resource was enabled for the Project. - :rtype: str - :raises ~azure.core.exceptions.ResourceNotFoundError: An Application Insights connection does not - exist for this Foundry project. - """ - if not self._connection_string: - - # TODO: Two REST APIs calls can be replaced by one if we have had REST API for get_with_credentials(connection_type=ConnectionType.APPLICATION_INSIGHTS) - # Returns an empty Iterable if no connections exits. - connections: AsyncIterable[Connection] = self._outer_instance.connections.list( - connection_type=ConnectionType.APPLICATION_INSIGHTS, - ) - - # Note: there can't be more than one AppInsights connection. - connection_name: Optional[str] = None - async for connection in connections: - connection_name = connection.name - break - if not connection_name: - raise ResourceNotFoundError("No Application Insights connection found.") - - connection = ( - await self._outer_instance.connections._get_with_credentials( # pylint: disable=protected-access - name=connection_name - ) - ) - - if isinstance(connection.credentials, ApiKeyCredentials): - if not connection.credentials.api_key: - raise ValueError("Application Insights connection does not have a connection string.") - self._connection_string = connection.credentials.api_key - else: - raise ValueError("Application Insights connection does not use API Key credentials.") - - return self._connection_string diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py index 2c528dea5d0d..eaf5ee2de82e 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_enums.py @@ -550,8 +550,8 @@ class RankerVersionType(str, Enum, metaclass=CaseInsensitiveEnumMeta): AUTO = "auto" """AUTO.""" - DEFAULT_2024_11_15 = "default-2024-11-15" - """DEFAULT_2024_11_15.""" + DEFAULT2024_11_15 = "default-2024-11-15" + """DEFAULT2024_11_15.""" class RecurrenceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): @@ -666,8 +666,8 @@ class ToolChoiceParamType(str, Enum, metaclass=CaseInsensitiveEnumMeta): """WEB_SEARCH_PREVIEW.""" COMPUTER_USE_PREVIEW = "computer_use_preview" """COMPUTER_USE_PREVIEW.""" - WEB_SEARCH_PREVIEW_2025_03_11 = "web_search_preview_2025_03_11" - """WEB_SEARCH_PREVIEW_2025_03_11.""" + WEB_SEARCH_PREVIEW2025_03_11 = "web_search_preview_2025_03_11" + """WEB_SEARCH_PREVIEW2025_03_11.""" IMAGE_GENERATION = "image_generation" """IMAGE_GENERATION.""" CODE_INTERPRETER = "code_interpreter" diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py index c9a06e9dacfa..f70f9d97a8d9 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_models.py @@ -8635,7 +8635,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): or a Literal["required"] type. :vartype mode: str or str :ivar tools: A list of tool definitions that the model should be allowed to call. For the - Responses API, the list of tool definitions might look like the following. Required. + Responses API, the list of tool definitions might look like: .. code-block:: json @@ -8643,7 +8643,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): { "type": "function", "name": "get_weather" }, { "type": "mcp", "server_label": "deepwiki" }, { "type": "image_generation" } - ] + ]. Required. :vartype tools: list[dict[str, any]] """ @@ -8656,7 +8656,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): Literal[\"required\"] type.""" tools: list[dict[str, Any]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """A list of tool definitions that the model should be allowed to call. For the Responses API, the - list of tool definitions might look like the following. Required. + list of tool definitions might look like: .. code-block:: json @@ -8664,7 +8664,7 @@ class ToolChoiceAllowed(ToolChoiceParam, discriminator="allowed_tools"): { \"type\": \"function\", \"name\": \"get_weather\" }, { \"type\": \"mcp\", \"server_label\": \"deepwiki\" }, { \"type\": \"image_generation\" } - ]""" + ]. Required.""" @overload def __init__( @@ -8933,12 +8933,12 @@ class ToolChoiceWebSearchPreview20250311(ToolChoiceParam, discriminator="web_sea """Indicates that the model should use a built-in tool to generate a response. `Learn more about built-in tools `_. - :ivar type: Required. WEB_SEARCH_PREVIEW_2025_03_11. - :vartype type: str or ~azure.ai.projects.models.WEB_SEARCH_PREVIEW_2025_03_11 + :ivar type: Required. WEB_SEARCH_PREVIEW2025_03_11. + :vartype type: str or ~azure.ai.projects.models.WEB_SEARCH_PREVIEW2025_03_11 """ - type: Literal[ToolChoiceParamType.WEB_SEARCH_PREVIEW_2025_03_11] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore - """Required. WEB_SEARCH_PREVIEW_2025_03_11.""" + type: Literal[ToolChoiceParamType.WEB_SEARCH_PREVIEW2025_03_11] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. WEB_SEARCH_PREVIEW2025_03_11.""" @overload def __init__( @@ -8954,7 +8954,7 @@ def __init__(self, mapping: Mapping[str, Any]) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) - self.type = ToolChoiceParamType.WEB_SEARCH_PREVIEW_2025_03_11 # type: ignore + self.type = ToolChoiceParamType.WEB_SEARCH_PREVIEW2025_03_11 # type: ignore class ToolDescription(_Model): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py index 658e4c90fb2f..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/models/_patch.py @@ -1,355 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from typing import Final, FrozenSet, List, Dict, Mapping, Optional, Any, Tuple -from azure.core.polling import LROPoller, AsyncLROPoller, PollingMethod, AsyncPollingMethod -from azure.core.polling.base_polling import ( - LROBasePolling, - OperationFailed, - _raise_if_bad_http_status_and_method, -) -from azure.core.polling.async_base_polling import AsyncLROBasePolling -from ._models import CustomCredential as CustomCredentialGenerated -from ..models import MemoryStoreUpdateCompletedResult, MemoryStoreUpdateResult -from ._enums import _FoundryFeaturesOptInKeys -_FOUNDRY_FEATURES_HEADER_NAME: Final[str] = "Foundry-Features" -"""The HTTP header name used to opt in to Foundry preview features.""" - -_BETA_OPERATION_FEATURE_HEADERS: Final[dict] = { - "evaluation_taxonomies": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "evaluators": _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value, - "insights": _FoundryFeaturesOptInKeys.INSIGHTS_V1_PREVIEW.value, - "memory_stores": _FoundryFeaturesOptInKeys.MEMORY_STORES_V1_PREVIEW.value, - "red_teams": _FoundryFeaturesOptInKeys.RED_TEAMS_V1_PREVIEW.value, - "schedules": _FoundryFeaturesOptInKeys.SCHEDULES_V1_PREVIEW.value, - "toolsets": _FoundryFeaturesOptInKeys.TOOLSET_V1_PREVIEW.value, -} -"""Foundry-Features header values keyed by beta sub-client property name.""" - - -def _has_header_case_insensitive(headers: Any, header_name: str) -> bool: - """Return True if headers already contains the provided header name. - - :param headers: The headers mapping to search. - :type headers: Any - :param header_name: The header name to look for (case-insensitive). - :type header_name: str - :return: True if the header is present, False otherwise. - :rtype: bool - """ - try: - header_name_lower = header_name.lower() - return any(str(key).lower() == header_name_lower for key in headers) - except Exception: # pylint: disable=broad-except - return False - - -class CustomCredential(CustomCredentialGenerated, discriminator="CustomKeys"): - """Custom credential definition. - - :ivar type: The credential type. Always equals CredentialType.CUSTOM. Required. - :vartype type: str or ~azure.ai.projects.models.CredentialType - :ivar credential_keys: The secret custom credential keys. Required. - :vartype credential_keys: dict[str, str] - """ - - credential_keys: Dict[str, str] - """The secret custom credential keys. Required.""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - - # Fix for GitHub issue https://github.com/Azure/azure-sdk-for-net/issues/52355 - # Although the issue was filed on C# Projects SDK, the same problem exists in Python SDK. - # Assume your Foundry project has a connection of type `Custom`, named "test_custom_connection", - # and you defined two public and two secrete (private) keys. When you get the connection, the response - # payload will look something like this: - # { - # "name": "test_custom_connection", - # "id": "/subscriptions/.../connections/test_custom_connection", - # "type": "CustomKeys", - # "target": "_", - # "isDefault": true, - # "credentials": { - # "nameofprivatekey1": "PrivateKey1", - # "nameofprivatekey2": "PrivateKey2", - # "type": "CustomKeys" - # }, - # "metadata": { - # "NameOfPublicKey1": "PublicKey1", - # "NameOfPublicKey2": "PublicKey2" - # } - # } - # We would like to add a new Dict property on the Python `credentials` object, named `credential_keys`, - # to hold all the secret keys. This is done by the line below. - if args and isinstance(args[0], Mapping): - self.credential_keys = {k: v for k, v in args[0].items() if k != "type"} - else: - self.credential_keys = {} - - -_FINISHED: Final[FrozenSet[str]] = frozenset(["completed", "superseded", "failed"]) -_FAILED: Final[FrozenSet[str]] = frozenset(["failed"]) - - -class _UpdateMemoriesLROPollingMethod(LROBasePolling): - """A custom polling method implementation for Memory Store updates.""" - - @property - def _current_body(self) -> MemoryStoreUpdateResult: - try: - return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) - except Exception: # pylint: disable=broad-exception-caught - return MemoryStoreUpdateResult() # type: ignore[call-overload] - - def finished(self) -> bool: - """Is this polling finished? - - :return: True/False for whether polling is complete. - :rtype: bool - """ - return self._finished(self.status()) - - @staticmethod - def _finished(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FINISHED - - @staticmethod - def _failed(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FAILED - - def get_continuation_token(self) -> str: - return self._current_body.update_id - - # pylint: disable=arguments-differ - def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] - try: - client = kwargs["client"] - except KeyError as exc: - raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc - - try: - deserialization_callback = kwargs["deserialization_callback"] - except KeyError as exc: - raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc - - return client, continuation_token, deserialization_callback - - def _poll(self) -> None: - """Poll status of operation so long as operation is incomplete and - we have an endpoint to query. - - :raises: OperationFailed if operation status 'Failed' or 'Canceled'. - :raises: BadStatus if response status invalid. - :raises: BadResponse if response invalid. - """ - - if not self.finished(): - self.update_status() - while not self.finished(): - self._delay() - self.update_status() - - if self._failed(self.status()): - raise OperationFailed("Operation failed or canceled") - - final_get_url = self._operation.get_final_get_url(self._pipeline_response) - if final_get_url: - self._pipeline_response = self.request_status(final_get_url) - _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) - - -class _AsyncUpdateMemoriesLROPollingMethod(AsyncLROBasePolling): - """A custom polling method implementation for Memory Store updates.""" - - @property - def _current_body(self) -> MemoryStoreUpdateResult: - try: - return MemoryStoreUpdateResult(self._pipeline_response.http_response.json()) - except Exception: # pylint: disable=broad-exception-caught - return MemoryStoreUpdateResult() # type: ignore[call-overload] - - def finished(self) -> bool: - """Is this polling finished? - - :return: True/False for whether polling is complete. - :rtype: bool - """ - return self._finished(self.status()) - - @staticmethod - def _finished(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FINISHED - - @staticmethod - def _failed(status) -> bool: - if hasattr(status, "value"): - status = status.value - return str(status).lower() in _FAILED - - def get_continuation_token(self) -> str: - return self._current_body.update_id - - # pylint: disable=arguments-differ - def from_continuation_token(self, continuation_token: str, **kwargs: Any) -> Tuple: # type: ignore[override] - try: - client = kwargs["client"] - except KeyError as exc: - raise ValueError("Need kwarg 'client' to be recreated from continuation_token") from exc - - try: - deserialization_callback = kwargs["deserialization_callback"] - except KeyError as exc: - raise ValueError("Need kwarg 'deserialization_callback' to be recreated from continuation_token") from exc - - return client, continuation_token, deserialization_callback - - async def _poll(self) -> None: - """Poll status of operation so long as operation is incomplete and - we have an endpoint to query. - - :raises: OperationFailed if operation status 'Failed' or 'Canceled'. - :raises: BadStatus if response status invalid. - :raises: BadResponse if response invalid. - """ - - if not self.finished(): - await self.update_status() - while not self.finished(): - await self._delay() - await self.update_status() - - if self._failed(self.status()): - raise OperationFailed("Operation failed or canceled") - - final_get_url = self._operation.get_final_get_url(self._pipeline_response) - if final_get_url: - self._pipeline_response = await self.request_status(final_get_url) - _raise_if_bad_http_status_and_method(self._pipeline_response.http_response) - - -class UpdateMemoriesLROPoller(LROPoller[MemoryStoreUpdateCompletedResult]): - """Custom LROPoller for Memory Store update operations.""" - - _polling_method: "_UpdateMemoriesLROPollingMethod" - - @property - def update_id(self) -> str: - """Returns the update ID associated with the long-running update memories operation. - - :return: Returns the update ID. - :rtype: str - """ - return self._polling_method._current_body.update_id # pylint: disable=protected-access - - @property - def superseded_by(self) -> Optional[str]: - """Returns the ID of the operation that superseded this update. - - :return: Returns the ID of the superseding operation, if it exists. - :rtype: Optional[str] - """ - return ( - self._polling_method._current_body.superseded_by # pylint: disable=protected-access - if self._polling_method._current_body # pylint: disable=protected-access - else None - ) - - @classmethod - def from_continuation_token( - cls, polling_method: PollingMethod[MemoryStoreUpdateCompletedResult], continuation_token: str, **kwargs: Any - ) -> "UpdateMemoriesLROPoller": - """Create a poller from a continuation token. - - :param polling_method: The polling strategy to adopt - :type polling_method: ~azure.core.polling.PollingMethod - :param continuation_token: An opaque continuation token - :type continuation_token: str - :return: An instance of UpdateMemoriesLROPoller - :rtype: UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. - """ - ( - client, - initial_response, - deserialization_callback, - ) = polling_method.from_continuation_token(continuation_token, **kwargs) - - return cls(client, initial_response, deserialization_callback, polling_method) - - -class AsyncUpdateMemoriesLROPoller(AsyncLROPoller[MemoryStoreUpdateCompletedResult]): - """Custom AsyncLROPoller for Memory Store update operations.""" - - _polling_method: "_AsyncUpdateMemoriesLROPollingMethod" - - @property - def update_id(self) -> str: - """Returns the update ID associated with the long-running update memories operation. - - :return: Returns the update ID. - :rtype: str - """ - return self._polling_method._current_body.update_id # pylint: disable=protected-access - - @property - def superseded_by(self) -> Optional[str]: - """Returns the ID of the operation that superseded this update. - - :return: Returns the ID of the superseding operation, if it exists. - :rtype: Optional[str] - """ - return ( - self._polling_method._current_body.superseded_by # pylint: disable=protected-access - if self._polling_method._current_body # pylint: disable=protected-access - else None - ) - - @classmethod - def from_continuation_token( - cls, - polling_method: AsyncPollingMethod[MemoryStoreUpdateCompletedResult], - continuation_token: str, - **kwargs: Any, - ) -> "AsyncUpdateMemoriesLROPoller": - """Create a poller from a continuation token. - - :param polling_method: The polling strategy to adopt - :type polling_method: ~azure.core.polling.PollingMethod - :param continuation_token: An opaque continuation token - :type continuation_token: str - :return: An instance of AsyncUpdateMemoriesLROPoller - :rtype: AsyncUpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: If the continuation token is invalid. - """ - ( - client, - initial_response, - deserialization_callback, - ) = polling_method.from_continuation_token(continuation_token, **kwargs) - - return cls(client, initial_response, deserialization_callback, polling_method) - - -__all__: List[str] = [ - "CustomCredential", - "UpdateMemoriesLROPoller", - "AsyncUpdateMemoriesLROPoller", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py index f320a58a57b0..d5b154dc719b 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_operations.py @@ -4799,10 +4799,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5232,10 +5229,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -5335,10 +5329,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -6391,10 +6382,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7062,7 +7050,7 @@ def _search_memories( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "options": options, "previous_search_id": previous_search_id, "scope": scope, @@ -7148,7 +7136,7 @@ def _update_memories_initial( if scope is _Unset: raise TypeError("missing required argument: scope") body = { - "items": items, + "items_property": items, "previous_update_id": previous_update_id, "scope": scope, "update_delay": update_delay, @@ -7595,10 +7583,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -7942,10 +7927,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( @@ -8245,10 +8227,7 @@ def prepare_request(next_link=None): ) _next_request_params["api-version"] = self._config.api_version _request = HttpRequest( - "GET", - urllib.parse.urljoin(next_link, _parsed_next_link.path), - params=_next_request_params, - headers=_headers, + "GET", urllib.parse.urljoin(next_link, _parsed_next_link.path), params=_next_request_params ) path_format_arguments = { "endpoint": self._serialize.url( diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py index 2330bf816c59..87676c65a8f0 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch.py @@ -1,142 +1,15 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- """Customize generated code here. Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ -from functools import wraps -import inspect -from typing import Any, Callable, List -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _BETA_OPERATION_FEATURE_HEADERS, _has_header_case_insensitive -from ._patch_agents import AgentsOperations -from ._patch_datasets import DatasetsOperations -from ._patch_evaluation_rules import EvaluationRulesOperations -from ._patch_evaluators import BetaEvaluatorsOperations -from ._patch_telemetry import TelemetryOperations -from ._patch_connections import ConnectionsOperations -from ._patch_memories import BetaMemoryStoresOperations -from ._operations import ( - BetaEvaluationTaxonomiesOperations, - BetaInsightsOperations, - BetaOperations as GeneratedBetaOperations, - BetaRedTeamsOperations, - BetaSchedulesOperations, - BetaToolsetsOperations, -) - -def _method_accepts_keyword_headers(method: Callable[..., Any]) -> bool: - try: - signature = inspect.signature(method) - except (TypeError, ValueError): - return False - - for parameter in signature.parameters.values(): - if parameter.name == "headers": - return True - if parameter.kind == inspect.Parameter.VAR_KEYWORD: - return True - - return False - - -class _OperationMethodHeaderProxy: - """Proxy that injects the Foundry-Features header into public operation method calls.""" - - def __init__(self, operation: Any, foundry_features_value: str): - object.__setattr__(self, "_operation", operation) - object.__setattr__(self, "_foundry_features_value", foundry_features_value) - - def __getattr__(self, name: str) -> Any: - attribute = getattr(self._operation, name) - - if name.startswith("_") or not callable(attribute) or not _method_accepts_keyword_headers(attribute): - return attribute - - @wraps(attribute) - def _wrapped(*args: Any, **kwargs: Any) -> Any: - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value} - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - try: - headers[_FOUNDRY_FEATURES_HEADER_NAME] = self._foundry_features_value - except Exception: # pylint: disable=broad-except - # Fall back to replacing invalid/immutable header containers. - kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: self._foundry_features_value, - } - - return attribute(*args, **kwargs) - - return _wrapped - - def __dir__(self) -> list: - return dir(self._operation) - - def __setattr__(self, name: str, value: Any) -> None: - setattr(self._operation, name, value) - - -class BetaOperations(GeneratedBetaOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`beta` attribute. - """ - - evaluation_taxonomies: BetaEvaluationTaxonomiesOperations - """:class:`~azure.ai.projects.operations.BetaEvaluationTaxonomiesOperations` operations""" - evaluators: BetaEvaluatorsOperations - """:class:`~azure.ai.projects.operations.BetaEvaluatorsOperations` operations""" - insights: BetaInsightsOperations - """:class:`~azure.ai.projects.operations.BetaInsightsOperations` operations""" - memory_stores: BetaMemoryStoresOperations - """:class:`~azure.ai.projects.operations.BetaMemoryStoresOperations` operations""" - red_teams: BetaRedTeamsOperations - """:class:`~azure.ai.projects.operations.BetaRedTeamsOperations` operations""" - schedules: BetaSchedulesOperations - """:class:`~azure.ai.projects.operations.BetaSchedulesOperations` operations""" - toolsets: BetaToolsetsOperations - """:class:`~azure.ai.projects.operations.BetaToolsetsOperations` operations""" - - def __init__(self, *args: Any, **kwargs: Any) -> None: - super().__init__(*args, **kwargs) - # Replace with patched class that includes upload() - self.evaluators = BetaEvaluatorsOperations(self._client, self._config, self._serialize, self._deserialize) - # Replace with patched class that includes begin_update_memories - self.memory_stores = BetaMemoryStoresOperations(self._client, self._config, self._serialize, self._deserialize) - - for property_name, foundry_features_value in _BETA_OPERATION_FEATURE_HEADERS.items(): - setattr( - self, - property_name, - _OperationMethodHeaderProxy(getattr(self, property_name), foundry_features_value), - ) - - -__all__: List[str] = [ - "AgentsOperations", - "BetaEvaluationTaxonomiesOperations", - "BetaEvaluatorsOperations", - "BetaInsightsOperations", - "BetaMemoryStoresOperations", - "BetaOperations", - "BetaRedTeamsOperations", - "BetaSchedulesOperations", - "BetaToolsetsOperations", - "ConnectionsOperations", - "DatasetsOperations", - "EvaluationRulesOperations", - "TelemetryOperations", -] # Add all objects you want publicly available to users at this package level +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level def patch_sdk(): diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py deleted file mode 100644 index 8c19e6279729..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_agents.py +++ /dev/null @@ -1,218 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression,pointless-string-statement -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, IO, overload, Final -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator import distributed_trace -from ._operations import AgentsOperations as GeneratedAgentsOperations, JSON, _Unset -from .. import models as _models -from ..models._enums import _AgentDefinitionOptInKeys, _FoundryFeaturesOptInKeys -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive - -""" -Example service response payload when the caller is trying to use a feature preview without opt-in flag (service error 403 (Forbidden)): - -"error": { - "code": "preview_feature_required", - "message": "Workflow agents is in preview. This operation requires the following opt-in preview feature(s): WorkflowAgents=V1Preview. Include the 'Foundry-Features: WorkflowAgents=V1Preview' header in your request.", - "param": "Foundry-Features", - "type": "invalid_request_error", - "details": [], - "additionalInfo": { - "request_id": "fdbc95804b7599404973026cd9ec732a" - } - } - -""" -_PREVIEW_FEATURE_REQUIRED_CODE: Final = "preview_feature_required" -_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE: Final = ( - '\n**Python SDK users**: This operation requires you to set "allow_preview=True" ' - "when calling the AIProjectClient constructor. " - "\nNote that preview features are under development and subject to change." -) -_AGENT_OPERATION_FEATURE_HEADERS: Final[str] = ",".join( - [ - _AgentDefinitionOptInKeys.HOSTED_AGENTS_V1_PREVIEW.value, - _AgentDefinitionOptInKeys.WORKFLOW_AGENTS_V1_PREVIEW.value, - _FoundryFeaturesOptInKeys.AGENT_ENDPOINT_V1_PREVIEW.value, - ] -) - - -class AgentsOperations(GeneratedAgentsOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`agents` attribute. - """ - - @overload - def create_version( - self, - agent_name: str, - *, - definition: _models.AgentDefinition, - content_type: str = "application/json", - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_version( - self, agent_name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_version( - self, agent_name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace - def create_version( - self, - agent_name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - definition: _models.AgentDefinition = _Unset, - metadata: Optional[dict[str, str]] = None, - description: Optional[str] = None, - **kwargs: Any, - ) -> _models.AgentVersionDetails: - """Create a new agent version. - - :param agent_name: The unique name that identifies the agent. Name can be used to - retrieve/update/delete the agent. - - * Must start and end with alphanumeric characters, - * Can contain hyphens in the middle - * Must not exceed 63 characters. Required. - :type agent_name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword definition: The agent definition. This can be a workflow, hosted agent, or a simple - agent definition. Required. - :paramtype definition: ~azure.ai.projects.models.AgentDefinition - :keyword metadata: Set of 16 key-value pairs that can be attached to an object. This can be - useful for storing additional information about the object in a structured - format, and querying for objects via API or the dashboard. - - Keys are strings with a maximum length of 64 characters. Values are strings - with a maximum length of 512 characters. Default value is None. - :paramtype metadata: dict[str, str] - :keyword description: A human-readable description of the agent. Default value is None. - :paramtype description: str - :return: AgentVersionDetails. The AgentVersionDetails is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.AgentVersionDetails - :raises ~azure.core.exceptions.HttpResponseError: - """ - - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = {_FOUNDRY_FEATURES_HEADER_NAME: _AGENT_OPERATION_FEATURE_HEADERS} - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _AGENT_OPERATION_FEATURE_HEADERS - kwargs["headers"] = headers - - try: - return super().create_version( - agent_name, - body, - definition=definition, - metadata=metadata, - description=description, - **kwargs, - ) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py deleted file mode 100644 index 581d13671516..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_connections.py +++ /dev/null @@ -1,63 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Optional, Any, Union -from azure.core.tracing.decorator import distributed_trace -from ._operations import ConnectionsOperations as ConnectionsOperationsGenerated -from ..models._models import Connection -from ..models._enums import ConnectionType - - -class ConnectionsOperations(ConnectionsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`connections` attribute. - """ - - @distributed_trace - def get(self, name: str, *, include_credentials: Optional[bool] = False, **kwargs: Any) -> Connection: - """Get a connection by name. - - :param name: The name of the connection. Required. - :type name: str - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ~azure.core.exceptions.HttpResponseError: - """ - if include_credentials: - return super()._get_with_credentials(name, **kwargs) - - return super()._get(name, **kwargs) - - @distributed_trace - def get_default( - self, connection_type: Union[str, ConnectionType], *, include_credentials: Optional[bool] = False, **kwargs: Any - ) -> Connection: - """Get the default connection for a given connection type. - - :param connection_type: The type of the connection. Required. - :type connection_type: str or ~azure.ai.projects.models.ConnectionType - :keyword include_credentials: Whether to include credentials in the response. Default is False. - :paramtype include_credentials: bool - :return: Connection. The Connection is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.Connection - :raises ValueError: If no default connection is found for the given type. - :raises ~azure.core.exceptions.HttpResponseError: - """ - connections = super().list(connection_type=connection_type, default_connection=True, **kwargs) - for connection in connections: - return self.get(connection.name, include_credentials=include_credentials, **kwargs) - raise ValueError(f"No default connection found for type: {connection_type}.") diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py deleted file mode 100644 index bf2c0db51271..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_datasets.py +++ /dev/null @@ -1,222 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import re -import logging -from typing import Any, Tuple, Optional -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob import ContainerClient -from azure.core.tracing.decorator import distributed_trace -from ._operations import DatasetsOperations as DatasetsOperationsGenerated -from ..models._models import ( - FileDatasetVersion, - FolderDatasetVersion, - PendingUploadRequest, - PendingUploadResponse, - PendingUploadType, -) - -logger = logging.getLogger(__name__) - - -class DatasetsOperations(DatasetsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`datasets` attribute. - """ - - # Internal helper method to create a new dataset and return a ContainerClient from azure-storage-blob package, - # to the dataset's blob storage. - def _create_dataset_and_get_its_container_client( - self, - name: str, - input_version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str]: - - pending_upload_response: PendingUploadResponse = self.pending_upload( - name=name, - version=input_version, - pending_upload_request=PendingUploadRequest( - pending_upload_type=PendingUploadType.BLOB_REFERENCE, - connection_name=connection_name, - ), - ) - output_version: str = input_version - - if not pending_upload_response.blob_reference: - raise ValueError("Blob reference is not present") - if not pending_upload_response.blob_reference.credential: - raise ValueError("SAS credential are not present") - if not pending_upload_response.blob_reference.credential.sas_uri: - raise ValueError("SAS URI is missing or empty") - - # For overview on Blob storage SDK in Python see: - # https://learn.microsoft.com/azure/storage/blobs/storage-quickstart-blobs-python - # https://learn.microsoft.com/azure/storage/blobs/storage-blob-upload-python - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-from-container-url - return ( - ContainerClient.from_container_url( - container_url=pending_upload_response.blob_reference.credential.sas_uri # Of the form: "https://.blob.core.windows.net/?" - ), - output_version, - ) - - @distributed_trace - def upload_file( - self, *, name: str, version: str, file_path: str, connection_name: Optional[str] = None, **kwargs: Any - ) -> FileDatasetVersion: - """Upload file to a blob storage, and create a dataset that references this file. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload the file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword file_path: The file name (including optional path) to be uploaded. Required. - :paramtype file_path: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FileDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - - pathlib_file_path = Path(file_path) - if not pathlib_file_path.exists(): - raise ValueError(f"The provided file `{file_path}` does not exist.") - if pathlib_file_path.is_dir(): - raise ValueError("The provided file is actually a folder. Use method `upload_folder` instead") - - container_client, output_version = self._create_dataset_and_get_its_container_client( - name=name, input_version=version, connection_name=connection_name - ) - - with container_client: - - with open(file=file_path, mode="rb") as data: - - blob_name = pathlib_file_path.name # Extract the file name from the path. - logger.debug( - "[upload_file] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-upload-blob - with container_client.upload_blob(name=blob_name, data=data, **kwargs) as blob_client: - logger.debug("[upload_file] Done uploading") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net//" - data_uri = urlsplit(blob_client.url)._replace(query="").geturl() - - dataset_version = self.create_or_update( - name=name, - version=output_version, - dataset_version=FileDatasetVersion( - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.blobclient?view=azure-python#azure-storage-blob-blobclient-url - # Per above doc the ".url" contains SAS token... should this be stripped away? - data_uri=data_uri, - ), - ) - - return dataset_version # type: ignore - - @distributed_trace - def upload_folder( - self, - *, - name: str, - version: str, - folder: str, - connection_name: Optional[str] = None, - file_pattern: Optional[re.Pattern] = None, - **kwargs: Any, - ) -> FolderDatasetVersion: - """Upload all files in a folder and its sub folders to a blob storage, while maintaining - relative paths, and create a dataset that references this folder. - This method uses the `ContainerClient.upload_blob` method from the azure-storage-blob package - to upload each file. Any keyword arguments provided will be passed to the `upload_blob` method. - - :keyword name: The name of the dataset. Required. - :paramtype name: str - :keyword version: The version identifier for the dataset. Required. - :paramtype version: str - :keyword folder: The folder name (including optional path) to be uploaded. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection, where the file should be uploaded. - If not specified, the default Azure Storage Account connection will be used. Optional. - :paramtype connection_name: str - :keyword file_pattern: A regex pattern to filter files to be uploaded. Only files matching the pattern - will be uploaded. Optional. - :paramtype file_pattern: re.Pattern - :return: The created dataset version. - :rtype: ~azure.ai.projects.models.FolderDatasetVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not Path(path_folder).exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if Path(path_folder).is_file(): - raise ValueError("The provided folder is actually a file. Use method `upload_file` instead.") - - container_client, output_version = self._create_dataset_and_get_its_container_client( - name=name, - input_version=version, - connection_name=connection_name, - ) - - with container_client: - - # Recursively traverse all files in the folder - files_uploaded: bool = False - for root, _, files in os.walk(folder): - for file in files: - if file_pattern and not file_pattern.search(file): - continue # Skip files that do not match the pattern - file_path = os.path.join(root, file) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") # Ensure correct format for Azure - logger.debug( - "[upload_folder] Start uploading file `%s` as blob `%s`.", - file_path, - blob_name, - ) - with open(file=file_path, mode="rb") as data: # Open the file for reading in binary mode - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-upload-blob - container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - logger.debug("[upload_folder] Done uploading file") - files_uploaded = True - logger.debug("[upload_folder] Done uploaded.") - - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - # Remove the SAS token from the URL (remove all query strings). - # The resulting format should be "https://.blob.core.windows.net/" - # See https://learn.microsoft.com/python/api/azure-storage-blob/azure.storage.blob.containerclient?view=azure-python#azure-storage-blob-containerclient-url - data_uri = urlsplit(container_client.url)._replace(query="").geturl() - - dataset_version = self.create_or_update( - name=name, - version=output_version, - dataset_version=FolderDatasetVersion(data_uri=data_uri), - ) - - return dataset_version # type: ignore diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py deleted file mode 100644 index 19dcee1ed6bd..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluation_rules.py +++ /dev/null @@ -1,130 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Any, IO, overload -from azure.core.exceptions import HttpResponseError -from azure.core.tracing.decorator import distributed_trace -from ._operations import EvaluationRulesOperations as GeneratedEvaluationRulesOperations, JSON -from ._patch_agents import _PREVIEW_FEATURE_REQUIRED_CODE, _PREVIEW_FEATURE_ADDED_ERROR_MESSAGE -from .. import models as _models -from ..models._enums import _FoundryFeaturesOptInKeys -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME, _has_header_case_insensitive - - -class EvaluationRulesOperations(GeneratedEvaluationRulesOperations): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`evaluation_rules` attribute. - """ - - @overload - def create_or_update( - self, id: str, evaluation_rule: _models.EvaluationRule, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_or_update( - self, id: str, evaluation_rule: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @overload - def create_or_update( - self, id: str, evaluation_rule: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Required. - :type evaluation_rule: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - ... - - @distributed_trace - def create_or_update( - self, id: str, evaluation_rule: Union[_models.EvaluationRule, JSON, IO[bytes]], **kwargs: Any - ) -> _models.EvaluationRule: - """Create or update an evaluation rule. - - :param id: Unique identifier for the evaluation rule. Required. - :type id: str - :param evaluation_rule: Evaluation rule resource. Is one of the following types: - EvaluationRule, JSON, IO[bytes] Required. - :type evaluation_rule: ~azure.ai.projects.models.EvaluationRule or JSON or IO[bytes] - :return: EvaluationRule. The EvaluationRule is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.EvaluationRule - :raises ~azure.core.exceptions.HttpResponseError: - """ - - if getattr(self._config, "allow_preview", False): - # Add Foundry-Features header if not already present - headers = kwargs.get("headers") - if headers is None: - kwargs["headers"] = { - _FOUNDRY_FEATURES_HEADER_NAME: _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - } - elif not _has_header_case_insensitive(headers, _FOUNDRY_FEATURES_HEADER_NAME): - headers[_FOUNDRY_FEATURES_HEADER_NAME] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - kwargs["headers"] = headers - - try: - return super().create_or_update(id, evaluation_rule, **kwargs) - except HttpResponseError as exc: - if exc.status_code == 403 and not self._config.allow_preview and exc.model is not None: - api_error_response = exc.model - if hasattr(api_error_response, "error") and api_error_response.error is not None: - if api_error_response.error.code == _PREVIEW_FEATURE_REQUIRED_CODE: - new_exc = HttpResponseError( - message=f"{exc.message} {_PREVIEW_FEATURE_ADDED_ERROR_MESSAGE}", - ) - new_exc.status_code = exc.status_code - new_exc.reason = exc.reason - new_exc.response = exc.response - new_exc.model = exc.model - raise new_exc from exc - raise diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py deleted file mode 100644 index 3f1c38d97b2b..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_evaluators.py +++ /dev/null @@ -1,258 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -import os -import logging -from typing import Any, Final, IO, Tuple, Optional, Union -from pathlib import Path -from urllib.parse import urlsplit -from azure.storage.blob import ContainerClient -from azure.core.tracing.decorator import distributed_trace -from azure.core.exceptions import HttpResponseError, ResourceNotFoundError -from ._operations import BetaEvaluatorsOperations as BetaEvaluatorsOperationsGenerated, JSON -from ..models._enums import _FoundryFeaturesOptInKeys -from ..models._patch import _FOUNDRY_FEATURES_HEADER_NAME -from ..models._models import ( - CodeBasedEvaluatorDefinition, - EvaluatorVersion, -) - -logger = logging.getLogger(__name__) - -_EVALUATORS_FOUNDRY_FEATURES_VALUE: Final[str] = _FoundryFeaturesOptInKeys.EVALUATIONS_V1_PREVIEW.value - - -class BetaEvaluatorsOperations(BetaEvaluatorsOperationsGenerated): - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`beta.evaluators` attribute. - """ - - @staticmethod - def _upload_folder_to_blob( - container_client: ContainerClient, - folder: str, - **kwargs: Any, - ) -> None: - """Walk *folder* and upload every eligible file to the blob container. - - Skips ``__pycache__``, ``.git``, ``.venv``, ``venv``, ``node_modules`` - directories and ``.pyc`` / ``.pyo`` files. - - :param container_client: The blob container client to upload files to. - :type container_client: ~azure.storage.blob.ContainerClient - :param folder: Path to the local folder containing files to upload. - :type folder: str - :raises ValueError: If the folder contains no uploadable files. - :raises HttpResponseError: Re-raised with a friendlier message on - ``AuthorizationPermissionMismatch``. - """ - skip_dirs = {"__pycache__", ".git", ".venv", "venv", "node_modules"} - skip_extensions = {".pyc", ".pyo"} - files_uploaded = False - - for root, dirs, files in os.walk(folder): - dirs[:] = [d for d in dirs if d not in skip_dirs] - for file_name in files: - if any(file_name.endswith(ext) for ext in skip_extensions): - continue - file_path = os.path.join(root, file_name) - blob_name = os.path.relpath(file_path, folder).replace("\\", "/") - logger.debug("[upload] Start uploading file `%s` as blob `%s`.", file_path, blob_name) - with open(file=file_path, mode="rb") as data: - try: - container_client.upload_blob(name=str(blob_name), data=data, **kwargs) - except HttpResponseError as e: - if getattr(e, "error_code", None) == "AuthorizationPermissionMismatch": - storage_account = urlsplit(container_client.url).hostname - raise HttpResponseError( - message=( - f"Failed to upload file '{blob_name}' to blob storage: " - f"permission denied. Ensure the identity that signed the SAS token " - f"has the 'Storage Blob Data Contributor' role on the storage account " - f"'{storage_account}'. " - f"Original error: {e.message}" - ), - response=e.response, - ) from e - raise - logger.debug("[upload] Done uploading file") - files_uploaded = True - - logger.debug("[upload] Done uploading all files.") - if not files_uploaded: - raise ValueError("The provided folder is empty.") - - @staticmethod - def _set_blob_uri( - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - blob_uri: str, - ) -> None: - """Set ``blob_uri`` on the evaluator version's definition. - - :param evaluator_version: The evaluator version object to update. - :type evaluator_version: Union[~azure.ai.projects.models.EvaluatorVersion, JSON, IO[bytes]] - :param blob_uri: The blob URI to set on the definition. - :type blob_uri: str - """ - if isinstance(evaluator_version, dict): - definition = evaluator_version.get("definition", {}) - if isinstance(definition, dict): - definition["blob_uri"] = blob_uri - elif isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - elif isinstance(evaluator_version, EvaluatorVersion): - definition = evaluator_version.definition - if isinstance(definition, CodeBasedEvaluatorDefinition): - definition.blob_uri = blob_uri - - def _start_pending_upload_and_get_container_client( - self, - name: str, - version: str, - connection_name: Optional[str] = None, - ) -> Tuple[ContainerClient, str, str]: - """Call startPendingUpload to get a SAS URI and return a ContainerClient and blob URI. - - :param name: The evaluator name. - :type name: str - :param version: The evaluator version. - :type version: str - :param connection_name: Optional storage account connection name. - :type connection_name: Optional[str] - :return: A tuple of (ContainerClient, version, blob_uri). - :rtype: Tuple[ContainerClient, str, str] - """ - - request_body: dict = {} - if connection_name: - request_body["connectionName"] = connection_name - - pending_upload_response = self.pending_upload( - name=name, - version=version, - pending_upload_request=request_body, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - # The service returns blobReferenceForConsumption - blob_ref = pending_upload_response.get("blobReferenceForConsumption") - if not blob_ref: - raise ValueError("Blob reference is not present in the pending upload response") - - credential = blob_ref.get("credential") if isinstance(blob_ref, dict) else None - if not credential: - raise ValueError("SAS credential is not present in the pending upload response") - - sas_uri = credential.get("sasUri") if isinstance(credential, dict) else None - if not sas_uri: - raise ValueError("SAS URI is missing or empty in the pending upload response") - - blob_uri = blob_ref.get("blobUri") if isinstance(blob_ref, dict) else None - if not blob_uri: - raise ValueError("Blob URI is missing or empty in the pending upload response") - - return ( - ContainerClient.from_container_url(container_url=sas_uri), - version, - blob_uri, - ) - - def _get_next_version(self, name: str) -> str: - """Get the next version number for an evaluator by fetching existing versions. - - :param name: The evaluator name. - :type name: str - :return: The next version number as a string. - :rtype: str - """ - try: - versions = list( - self.list_versions( - name=name, headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE} - ) - ) - if versions: - numeric_versions = [] - for v in versions: - ver = v.get("version") if isinstance(v, dict) else getattr(v, "version", None) - if ver and ver.isdigit(): - numeric_versions.append(int(ver)) - if numeric_versions: - return str(max(numeric_versions) + 1) - return "1" - except ResourceNotFoundError: - return "1" - - @distributed_trace - def upload( - self, - name: str, - evaluator_version: Union[EvaluatorVersion, JSON, IO[bytes]], - *, - folder: str, - connection_name: Optional[str] = None, - **kwargs: Any, - ) -> EvaluatorVersion: - """Upload all files in a folder to blob storage and create a code-based evaluator version - that references the uploaded code. - - This method calls startPendingUpload to get a SAS URI, uploads files from the folder - to blob storage, then creates an evaluator version referencing the uploaded blob. - - The version is automatically determined by incrementing the latest existing version. - - :param name: The name of the evaluator. Required. - :type name: str - :param evaluator_version: The evaluator version definition. This is the same object accepted - by ``create_version``. Is one of the following types: EvaluatorVersion, JSON, - IO[bytes]. Required. - :type evaluator_version: ~azure.ai.projects.models.EvaluatorVersion or JSON or IO[bytes] - :keyword folder: Path to the folder containing the evaluator Python code. Required. - :paramtype folder: str - :keyword connection_name: The name of an Azure Storage Account connection where the files - should be uploaded. If not specified, the default Azure Storage Account connection will be - used. Optional. - :paramtype connection_name: str - :return: The created evaluator version. - :rtype: ~azure.ai.projects.models.EvaluatorVersion - :raises ~azure.core.exceptions.HttpResponseError: If an error occurs during the HTTP request. - """ - path_folder = Path(folder) - if not path_folder.exists(): - raise ValueError(f"The provided folder `{folder}` does not exist.") - if path_folder.is_file(): - raise ValueError("The provided path is a file, not a folder.") - - version = self._get_next_version(name) - logger.info("[upload] Auto-resolved version to '%s'.", version) - - # Get SAS URI via startPendingUpload - container_client, _, blob_uri = self._start_pending_upload_and_get_container_client( - name=name, - version=version, - connection_name=connection_name, - ) - - with container_client: - self._upload_folder_to_blob(container_client, folder, **kwargs) - self._set_blob_uri(evaluator_version, blob_uri) - - result = self.create_version( - name=name, - evaluator_version=evaluator_version, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _EVALUATORS_FOUNDRY_FEATURES_VALUE}, - ) - - return result diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py deleted file mode 100644 index 33b932900a35..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_memories.py +++ /dev/null @@ -1,414 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Union, Optional, Any, List, overload, IO, cast -from openai.types.responses import ResponseInputParam -from azure.core.tracing.decorator import distributed_trace -from azure.core.polling import NoPolling -from azure.core.utils import case_insensitive_dict -from .. import models as _models -from ..models import ( - MemoryStoreOperationUsage, - ResponseUsageInputTokensDetails, - ResponseUsageOutputTokensDetails, - MemoryStoreUpdateCompletedResult, - UpdateMemoriesLROPoller, -) -from ..models._patch import ( - _UpdateMemoriesLROPollingMethod, - _FOUNDRY_FEATURES_HEADER_NAME, - _BETA_OPERATION_FEATURE_HEADERS, -) -from ._operations import JSON, _Unset, ClsType, BetaMemoryStoresOperations as GenerateBetaMemoryStoresOperations -from .._validation import api_version_validation -from .._utils.model_base import _deserialize, _serialize - - -def _serialize_memory_input_items( - items: Optional[Union[str, ResponseInputParam]], -) -> Optional[List[dict[str, Any]]]: - """Serialize OpenAI response input items to the payload shape expected by memory APIs. - - :param items: The items to serialize. Can be a plain string or an OpenAI ResponseInputParam. - :type items: Optional[Union[str, openai.types.responses.ResponseInputParam]] - :return: A list of serialized item dictionaries, or None if items is None. - :rtype: Optional[List[dict[str, Any]]] - """ - - if items is None: - return None - - if isinstance(items, str): - return [{"role": "user", "type": "message", "content": items}] - - if not isinstance(items, list): - raise TypeError("items must serialize to a list of dictionaries.") - - serialized_items: List[dict[str, Any]] = [] - for item in items: - if hasattr(item, "model_dump"): - item = cast(Any, item).model_dump() - elif hasattr(item, "as_dict"): - item = cast(Any, item).as_dict() - - serialized_item = _serialize(item) - if not isinstance(serialized_item, dict): - raise TypeError("items must serialize to a dictionary .") - serialized_items.append(serialized_item) - return serialized_items - - -class BetaMemoryStoresOperations(GenerateBetaMemoryStoresOperations): - - # A message or list of messages to store in memory. When using a list, each item needs to correspond to a dictionary with `role`, `content` and `type` properties (with type equals `message`). For example: {\"role\": \"user\", \"type\": \"message\", \"content\": \"my user message\"}" - @overload - def search_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def search_memories( - self, name: str, body: JSON, *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def search_memories( - self, name: str, body: IO[bytes], *, content_type: str = "application/json", **kwargs: Any - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - def search_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_search_id: Optional[str] = None, - options: Optional[_models.MemorySearchOptions] = None, - **kwargs: Any, - ) -> _models.MemoryStoreSearchResult: - """Search for relevant memories from a memory store based on conversation context. - - :param name: The name of the memory store to search. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages used to extract relevant memories. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_search_id: The unique ID of the previous search request, enabling incremental - memory search from where the last operation left off. Default value is None. - :paramtype previous_search_id: str - :keyword options: Memory search options. Default value is None. - :paramtype options: ~azure.ai.projects.models.MemorySearchOptions - :return: MemoryStoreSearchResult. The MemoryStoreSearchResult is compatible with MutableMapping - :rtype: ~azure.ai.projects.models.MemoryStoreSearchResult - :raises ~azure.core.exceptions.HttpResponseError: - """ - return super()._search_memories( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_search_id=previous_search_id, - options=options, - **kwargs, - ) - - @overload - def begin_update_memories( - self, - name: str, - *, - scope: str, - content_type: str = "application/json", - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def begin_update_memories( - self, - name: str, - body: JSON, - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: JSON - :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @overload - def begin_update_memories( - self, - name: str, - body: IO[bytes], - *, - content_type: str = "application/json", - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Required. - :type body: IO[bytes] - :keyword content_type: Body Parameter content-type. Content type parameter for binary body. - Default value is "application/json". - :paramtype content_type: str - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - - @distributed_trace - @api_version_validation( - method_added_on="v1", - params_added_on={"v1": ["api_version", "name", "content_type", "accept"]}, - api_versions_list=["v1"], - ) - def begin_update_memories( - self, - name: str, - body: Union[JSON, IO[bytes]] = _Unset, - *, - scope: str = _Unset, - items: Optional[Union[str, ResponseInputParam]] = None, - previous_update_id: Optional[str] = None, - update_delay: Optional[int] = None, - **kwargs: Any, - ) -> UpdateMemoriesLROPoller: - """Update memory store with conversation memories. - - :param name: The name of the memory store to update. Required. - :type name: str - :param body: Is either a JSON type or a IO[bytes] type. Required. - :type body: JSON or IO[bytes] - :keyword scope: The namespace that logically groups and isolates memories, such as a user ID. - Required. - :paramtype scope: str - :keyword items: A message or list of messages you would like to store in memory. When using a - list, each item needs to correspond to a dictionary with `role`, `content` and `type` - keys. For example: {"role": "user", "type": "message", "content": "my user message"}. - Only messages with `type` equals `message` are currently processed. Others are ignored. - Default value is None. - :paramtype items: Union[str, openai.types.responses.ResponseInputParam] - :keyword previous_update_id: The unique ID of the previous update request, enabling incremental - memory updates from where the last operation left off. Default value is None. - :paramtype previous_update_id: str - :keyword update_delay: Timeout period before processing the memory update in seconds. - If a new update request is received during this period, it will cancel the current request and - reset the timeout. - Set to 0 to immediately trigger the update without delay. - Defaults to 300 (5 minutes). Default value is None. - :paramtype update_delay: int - :return: An instance of UpdateMemoriesLROPoller that returns MemoryStoreUpdateCompletedResult. The - MemoryStoreUpdateCompletedResult is compatible with MutableMapping - :rtype: - ~azure.ai.projects.models.UpdateMemoriesLROPoller - :raises ~azure.core.exceptions.HttpResponseError: - """ - _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) - _params = kwargs.pop("params", {}) or {} - - content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) - cls: ClsType[MemoryStoreUpdateCompletedResult] = kwargs.pop("cls", None) - polling = kwargs.pop("polling", True) - if not isinstance(polling, bool): - raise TypeError("polling must be of type bool.") - lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) - cont_token: Optional[str] = kwargs.pop("continuation_token", None) - if cont_token is None: - raw_result = self._update_memories_initial( - name=name, - body=body, - scope=scope, - items=_serialize_memory_input_items(items), - previous_update_id=previous_update_id, - update_delay=update_delay, - content_type=content_type, - cls=lambda x, y, z: x, - headers=_headers, - params=_params, - **kwargs, - ) - raw_result.http_response.read() # type: ignore - - raw_result.http_response.status_code = 202 # type: ignore - raw_result.http_response.headers["Operation-Location"] = ( # type: ignore - f"{self._config.endpoint}/memory_stores/{name}/updates/{raw_result.http_response.json().get('update_id')}?api-version=v1" # type: ignore - ) - - kwargs.pop("error_map", None) - - def get_long_running_output(pipeline_response): - response_headers = {} - response = pipeline_response.http_response - response_headers["Operation-Location"] = self._deserialize( - "str", response.headers.get("Operation-Location") - ) - - deserialized = _deserialize(MemoryStoreUpdateCompletedResult, response.json().get("result", None)) - if deserialized is None: - usage = MemoryStoreOperationUsage( - embedding_tokens=0, - input_tokens=0, - input_tokens_details=ResponseUsageInputTokensDetails(cached_tokens=0), - output_tokens=0, - output_tokens_details=ResponseUsageOutputTokensDetails(reasoning_tokens=0), - total_tokens=0, - ) - deserialized = MemoryStoreUpdateCompletedResult(memory_operations=[], usage=usage) - if cls: - return cls(pipeline_response, deserialized, response_headers) # type: ignore - return deserialized - - path_format_arguments = { - "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), - } - - if polling: - polling_method: _UpdateMemoriesLROPollingMethod = _UpdateMemoriesLROPollingMethod( - lro_delay, - path_format_arguments=path_format_arguments, - headers={_FOUNDRY_FEATURES_HEADER_NAME: _BETA_OPERATION_FEATURE_HEADERS["memory_stores"]}, - **kwargs, - ) - else: - polling_method = cast(_UpdateMemoriesLROPollingMethod, NoPolling()) - - if cont_token: - return UpdateMemoriesLROPoller.from_continuation_token( - polling_method=polling_method, - continuation_token=cont_token, - client=self._client, - deserialization_callback=get_long_running_output, - ) - - return UpdateMemoriesLROPoller( - self._client, - raw_result, # type: ignore[possibly-undefined] - get_long_running_output, - polling_method, # pylint: disable=possibly-used-before-assignment - ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py b/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py deleted file mode 100644 index 158506d22235..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/operations/_patch_telemetry.py +++ /dev/null @@ -1,69 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" - -from typing import Optional, Iterable -from azure.core.exceptions import ResourceNotFoundError -from azure.core.tracing.decorator import distributed_trace -from ..models._models import Connection, ApiKeyCredentials -from ..models._enums import ConnectionType - - -class TelemetryOperations: - """ - .. warning:: - **DO NOT** instantiate this class directly. - - Instead, you should access the following operations through - :class:`~azure.ai.projects.AIProjectClient`'s - :attr:`telemetry` attribute. - """ - - _connection_string: Optional[str] = None - - def __init__(self, outer_instance: "azure.ai.projects.AIProjectClient") -> None: # type: ignore[name-defined] - self._outer_instance = outer_instance - - @distributed_trace - def get_application_insights_connection_string(self) -> str: # pylint: disable=name-too-long - """Get the Application Insights connection string associated with the Project's Application Insights resource. - - :return: The Application Insights connection string if a the resource was enabled for the Project. - :rtype: str - :raises ~azure.core.exceptions.ResourceNotFoundError: An Application Insights connection does not - exist for this Foundry project. - """ - if not self._connection_string: - - # TODO: Two REST APIs calls can be replaced by one if we have had REST API for get_with_credentials(connection_type=ConnectionType.APPLICATION_INSIGHTS) - # Returns an empty Iterable if no connections exits. - connections: Iterable[Connection] = self._outer_instance.connections.list( - connection_type=ConnectionType.APPLICATION_INSIGHTS, - ) - - # Note: there can't be more than one AppInsights connection. - connection_name: Optional[str] = None - for connection in connections: - connection_name = connection.name - break - if not connection_name: - raise ResourceNotFoundError("No Application Insights connection found.") - - connection = self._outer_instance.connections._get_with_credentials( # pylint: disable=protected-access - name=connection_name - ) - - if isinstance(connection.credentials, ApiKeyCredentials): - if not connection.credentials.api_key: - raise ValueError("Application Insights connection does not have a connection string.") - self._connection_string = connection.credentials.api_key - else: - raise ValueError("Application Insights connection does not use API Key credentials.") - - return self._connection_string diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py deleted file mode 100644 index 4c2b14d84727..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# Code generated by Microsoft (R) Python Code Generator. -# Changes may cause incorrect behavior and will be lost if the code is regenerated. -# -------------------------------------------------------------------------- - -from ._ai_project_instrumentor import AIProjectInstrumentor -from ._trace_function import trace_function - -__all__ = ["AIProjectInstrumentor", "trace_function"] diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py deleted file mode 100644 index 1a22ca314704..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_ai_project_instrumentor.py +++ /dev/null @@ -1,1535 +0,0 @@ -# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks,docstring-missing-param,docstring-should-be-keyword -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -import copy -import functools -import importlib -import json -import logging -import os -from datetime import datetime -from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING -from urllib.parse import urlparse -from azure.ai.projects.models._models import Tool -from azure.core import CaseInsensitiveEnumMeta # type: ignore -from azure.core.settings import settings -from azure.core.tracing import AbstractSpan -from ._utils import ( - AGENTS_PROVIDER, - ERROR_TYPE, - GEN_AI_AGENT_DESCRIPTION, - GEN_AI_AGENT_ID, - GEN_AI_AGENT_NAME, - GEN_AI_EVENT_CONTENT, - GEN_AI_INPUT_MESSAGES, - GEN_AI_MESSAGE_ID, - GEN_AI_MESSAGE_STATUS, - GEN_AI_OPERATION_NAME, - GEN_AI_OUTPUT_MESSAGES, - GEN_AI_PROVIDER_NAME, - GEN_AI_SYSTEM_MESSAGE, - GEN_AI_SYSTEM_INSTRUCTION_EVENT, - GEN_AI_THREAD_ID, - GEN_AI_THREAD_RUN_ID, - GEN_AI_USAGE_INPUT_TOKENS, - GEN_AI_USAGE_OUTPUT_TOKENS, - GEN_AI_RUN_STEP_START_TIMESTAMP, - GEN_AI_RUN_STEP_END_TIMESTAMP, - GEN_AI_RUN_STEP_STATUS, - GEN_AI_AGENT_VERSION, - GEN_AI_AGENT_HOSTED_CPU, - GEN_AI_AGENT_HOSTED_MEMORY, - GEN_AI_AGENT_HOSTED_IMAGE, - GEN_AI_AGENT_HOSTED_PROTOCOL, - GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION, - AGENT_TYPE_PROMPT, - AGENT_TYPE_WORKFLOW, - AGENT_TYPE_HOSTED, - AGENT_TYPE_UNKNOWN, - GEN_AI_AGENT_TYPE, - ERROR_MESSAGE, - OperationName, - start_span, - _get_use_message_events, -) -from ._responses_instrumentor import _ResponsesInstrumentorPreview - -_Unset: Any = object() - -logger = logging.getLogger(__name__) - -try: - # pylint: disable = no-name-in-module - from opentelemetry.trace import Span, StatusCode - - _tracing_library_available = True -except ModuleNotFoundError: - _tracing_library_available = False - -if TYPE_CHECKING: - from .. import _types - -__all__ = [ - "AIProjectInstrumentor", -] - -_projects_traces_enabled: bool = False -_trace_agents_content: bool = False -_trace_context_propagation_enabled: bool = False -_trace_context_baggage_propagation_enabled: bool = False - - -def _inject_trace_context_sync(request): - """Synchronous event hook to inject trace context (traceparent) into outgoing requests. - - :param request: The httpx Request object. - :type request: httpx.Request - """ - try: - from opentelemetry import propagate - - carrier = dict(request.headers) - propagate.inject(carrier) - for key, value in carrier.items(): - key_lower = key.lower() - # Always include traceparent and tracestate - # Only include baggage if explicitly enabled - if key_lower in ("traceparent", "tracestate"): - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - elif key_lower == "baggage" and _trace_context_baggage_propagation_enabled: - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to inject trace context: %s", e) - - -async def _inject_trace_context_async(request): - """Async event hook to inject trace context (traceparent) into outgoing requests. - - :param request: The httpx Request object. - :type request: httpx.Request - """ - try: - from opentelemetry import propagate - - carrier = dict(request.headers) - propagate.inject(carrier) - for key, value in carrier.items(): - key_lower = key.lower() - # Always include traceparent and tracestate - # Only include baggage if explicitly enabled - if key_lower in ("traceparent", "tracestate"): - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - elif key_lower == "baggage" and _trace_context_baggage_propagation_enabled: - if key_lower not in [h.lower() for h in request.headers.keys()]: - request.headers[key] = value - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to inject trace context: %s", e) - - -def _enable_trace_propagation_for_openai_client(openai_client): - """Enable trace context propagation for an OpenAI client. - - This function hooks into the httpx client used by the OpenAI SDK to inject - trace context headers (traceparent, tracestate) into outgoing HTTP requests. - This ensures that client-side spans and server-side spans share the same trace ID. - - :param openai_client: The OpenAI client instance. - :type openai_client: Any - """ - try: - # Access the underlying httpx client - if hasattr(openai_client, "_client"): - httpx_client = openai_client._client # pylint: disable=protected-access - - # Check if the client has event hooks support - if hasattr(httpx_client, "_event_hooks"): - event_hooks = httpx_client._event_hooks # pylint: disable=protected-access - - # Determine if this is an async client - is_async = hasattr(httpx_client, "__aenter__") - - # Add appropriate hook based on client type - if "request" in event_hooks: - hook_to_add = _inject_trace_context_async if is_async else _inject_trace_context_sync - - # Check if our hook is already registered to avoid duplicates - if hook_to_add not in event_hooks["request"]: - event_hooks["request"].append(hook_to_add) - logger.debug("Enabled trace propagation for %s OpenAI client", "async" if is_async else "sync") - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to enable trace propagation for OpenAI client: %s", e) - - -class TraceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): # pylint: disable=C4747 - """An enumeration class to represent different types of traces.""" - - AGENTS = "Agents" - PROJECT = "Project" - - -class AIProjectInstrumentor: - """ - A class for managing the trace instrumentation of the AIProjectClient. - - This class allows enabling or disabling tracing for AI Projects - and provides functionality to check whether instrumentation is active. - - .. warning:: - GenAI tracing is an experimental preview feature. To use this feature, you must set the - environment variable ``AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`` (case insensitive) - before calling the ``instrument()`` method. Spans, attributes, and events may be modified - in future versions. - - """ - - def __init__(self) -> None: - if not _tracing_library_available: - raise ModuleNotFoundError( - "Azure Core Tracing Opentelemetry is not installed. " - "Please install it using 'pip install azure-core-tracing-opentelemetry'" - ) - # We could support different semantic convention versions from the same library - # and have a parameter that specifies the version to use. - self._impl = _AIAgentsInstrumentorPreview() - self._responses_impl = _ResponsesInstrumentorPreview() - - def instrument( - self, - enable_content_recording: Optional[bool] = None, - enable_trace_context_propagation: Optional[bool] = None, - enable_baggage_propagation: Optional[bool] = None, - ) -> None: - """ - Enable trace instrumentation for AIProjectClient. - - .. warning:: - GenAI tracing is an experimental preview feature. To use this feature, you must set the - environment variable ``AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true`` (case insensitive). - If this environment variable is not set or set to any value other than "true", - instrumentation will not be enabled and this method will return immediately without effect. - Spans, attributes, and events may be modified in future versions. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - :param enable_trace_context_propagation: Whether to enable automatic trace context propagation - to OpenAI SDK HTTP requests. When enabled, traceparent and tracestate headers will be injected - into requests made by OpenAI clients obtained via get_openai_client(), allowing server-side - spans to be correlated with client-side spans. `True` will enable it, `False` will - disable it. If no value is provided, then the value read from environment variable - AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION is used. If the environment - variable is not found, then the value will default to `True`. - Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after - the change; previously acquired clients are unaffected. - :type enable_trace_context_propagation: bool, optional - :param enable_baggage_propagation: Whether to include baggage headers in trace context propagation. - Only applies when enable_trace_context_propagation is True. `True` will enable baggage propagation, - `False` will disable it. If no value is provided, then the value read from environment variable - AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the environment - variable is not found, then the value will default to `False`. - Note: Baggage may contain sensitive application data. This value is evaluated dynamically on - each request, so changes apply immediately — but only for OpenAI clients that had the trace - context propagation hook registered at acquisition time (i.e. clients obtained via - get_openai_client() while enable_trace_context_propagation was True). Clients acquired when - trace context propagation was disabled will never propagate baggage regardless of this value. - :type enable_baggage_propagation: bool, optional - - """ - # Check experimental feature gate - experimental_enabled = os.environ.get("AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING", "false") - if experimental_enabled.lower() != "true": - logger.warning( - "GenAI tracing is not enabled. Set environment variable " - "AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true to enable this experimental feature." - ) - return - - self._impl.instrument(enable_content_recording, enable_trace_context_propagation, enable_baggage_propagation) - self._responses_impl.instrument(enable_content_recording) - - def uninstrument(self) -> None: - """ - Remove trace instrumentation for AIProjectClient. - - This method removes any active instrumentation, stopping the tracing - of AIProjectClient methods. - """ - self._impl.uninstrument() - self._responses_impl.uninstrument() - - def is_instrumented(self) -> bool: - """ - Check if trace instrumentation for AIProjectClient is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._impl.is_instrumented() - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content recording is enabled. - :rtype: bool - """ - return self._impl.is_content_recording_enabled() - - -class _AIAgentsInstrumentorPreview: - # pylint: disable=R0904 - """ - A class for managing the trace instrumentation of AI Agents. - - This class allows enabling or disabling tracing for AI Agents - and provides functionality to check whether instrumentation is active. - - .. note:: - This is a private implementation class. Use the public AIProjectInstrumentor class instead. - """ - - def _str_to_bool(self, s): - if s is None: - return False - return str(s).lower() == "true" - - def instrument( - self, - enable_content_recording: Optional[bool] = None, - enable_trace_context_propagation: Optional[bool] = None, - enable_baggage_propagation: Optional[bool] = None, - ): - """ - Enable trace instrumentation for AI Agents. - - .. note:: - This is a private implementation method. Use AIProjectInstrumentor.instrument() instead. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. - `True` will enable it, `False` will disable it. If no value is provided, then the - value read from environment variable AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION - is used. If the environment variable is not found, then the value will default to `True`. - Note: Changing this value only affects OpenAI clients obtained via get_openai_client() after - the change; previously acquired clients are unaffected. - :type enable_trace_context_propagation: bool, optional - :param enable_baggage_propagation: Whether to include baggage in trace context propagation. - Only applies when enable_trace_context_propagation is True. `True` will enable it, `False` - will disable it. If no value is provided, then the value read from environment variable - AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE is used. If the - environment variable is not found, then the value will default to `False`. - Note: This value is evaluated dynamically on each request, so changes apply immediately — - but only for OpenAI clients that had the trace context propagation hook registered at - acquisition time (i.e. clients obtained via get_openai_client() while - enable_trace_context_propagation was True). Clients acquired when trace context propagation - was disabled will never propagate baggage regardless of this value. - :type enable_baggage_propagation: bool, optional - - """ - if enable_content_recording is None: - - var_value = os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT") - enable_content_recording = self._str_to_bool(var_value) - - if enable_trace_context_propagation is None: - var_value = os.environ.get("AZURE_TRACING_GEN_AI_ENABLE_TRACE_CONTEXT_PROPAGATION") - enable_trace_context_propagation = True if var_value is None else self._str_to_bool(var_value) - - if enable_baggage_propagation is None: - var_value = os.environ.get("AZURE_TRACING_GEN_AI_TRACE_CONTEXT_PROPAGATION_INCLUDE_BAGGAGE") - enable_baggage_propagation = self._str_to_bool(var_value) - - if not self.is_instrumented(): - self._instrument_projects( - enable_content_recording, enable_trace_context_propagation, enable_baggage_propagation - ) - else: - self._set_enable_content_recording(enable_content_recording=enable_content_recording) - self._set_enable_trace_context_propagation( - enable_trace_context_propagation=enable_trace_context_propagation - ) - self._set_enable_baggage_propagation(enable_baggage_propagation=enable_baggage_propagation) - - def uninstrument(self): - """ - Disable trace instrumentation for AI Projects. - - This method removes any active instrumentation, stopping the tracing - of AI Projects. - """ - if self.is_instrumented(): - self._uninstrument_projects() - - def is_instrumented(self): - """ - Check if trace instrumentation for AI Projects is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._is_instrumented() - - def set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - """This function sets the content recording value. - - :param enable_content_recording: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_recording: bool - """ - self._set_enable_content_recording(enable_content_recording=enable_content_recording) - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content tracing is enabled. - :rtype bool - """ - return self._is_content_recording_enabled() - - def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None: - for attr in attrs: - key, value = attr - if value is not None: - span.add_attribute(key, value) - - def _parse_url(self, url): - parsed = urlparse(url) - server_address = parsed.hostname - port = parsed.port - return server_address, port - - def _remove_function_call_names_and_arguments(self, tool_calls: list) -> list: - tool_calls_copy = copy.deepcopy(tool_calls) - for tool_call in tool_calls_copy: - if "function" in tool_call: - if "name" in tool_call["function"]: - del tool_call["function"]["name"] - if "arguments" in tool_call["function"]: - del tool_call["function"]["arguments"] - if not tool_call["function"]: - del tool_call["function"] - return tool_calls_copy - - def _create_event_attributes( - self, - thread_id: Optional[str] = None, - agent_id: Optional[str] = None, - thread_run_id: Optional[str] = None, - message_id: Optional[str] = None, - message_status: Optional[str] = None, - run_step_status: Optional[str] = None, - created_at: Optional[datetime] = None, - completed_at: Optional[datetime] = None, - cancelled_at: Optional[datetime] = None, - failed_at: Optional[datetime] = None, - run_step_last_error: Optional[Any] = None, - usage: Optional[Any] = None, - ) -> Dict[str, Any]: - attrs: Dict[str, Any] = {GEN_AI_PROVIDER_NAME: AGENTS_PROVIDER} - if thread_id: - attrs[GEN_AI_THREAD_ID] = thread_id - - if agent_id: - attrs[GEN_AI_AGENT_ID] = agent_id - - if thread_run_id: - attrs[GEN_AI_THREAD_RUN_ID] = thread_run_id - - if message_id: - attrs[GEN_AI_MESSAGE_ID] = message_id - - if message_status: - attrs[GEN_AI_MESSAGE_STATUS] = self._status_to_string(message_status) - - if run_step_status: - attrs[GEN_AI_RUN_STEP_STATUS] = self._status_to_string(run_step_status) - - if created_at: - if isinstance(created_at, datetime): - attrs[GEN_AI_RUN_STEP_START_TIMESTAMP] = created_at.isoformat() - else: - # fallback in case integer or string gets passed - attrs[GEN_AI_RUN_STEP_START_TIMESTAMP] = str(created_at) - - end_timestamp = None - if completed_at: - end_timestamp = completed_at - elif cancelled_at: - end_timestamp = cancelled_at - elif failed_at: - end_timestamp = failed_at - - if isinstance(end_timestamp, datetime): - attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = end_timestamp.isoformat() - elif end_timestamp: - # fallback in case int or string gets passed - attrs[GEN_AI_RUN_STEP_END_TIMESTAMP] = str(end_timestamp) - - if run_step_last_error: - attrs[ERROR_MESSAGE] = run_step_last_error.message - attrs[ERROR_TYPE] = run_step_last_error.code - - if usage: - attrs[GEN_AI_USAGE_INPUT_TOKENS] = usage.prompt_tokens - attrs[GEN_AI_USAGE_OUTPUT_TOKENS] = usage.completion_tokens - - return attrs - - def add_thread_message_event( - self, - span, - message: Any, - usage: Optional[Any] = None, - ) -> None: - - content_body: Optional[Union[str, Dict[str, Any]]] = None - if _trace_agents_content: - # Handle processed dictionary messages - if isinstance(message, dict): - content = message.get("content") - if content: - content_body = content - - role = "unknown" - if isinstance(message, dict): - role = message.get("role", "unknown") - elif hasattr(message, "role"): - role = getattr(message, "role", "unknown") - - self._add_message_event( - span, - role, - content_body, - attachments=( - message.get("attachments") if isinstance(message, dict) else getattr(message, "attachments", None) - ), - thread_id=(message.get("thread_id") if isinstance(message, dict) else getattr(message, "thread_id", None)), - agent_id=(message.get("agent_id") if isinstance(message, dict) else getattr(message, "agent_id", None)), - message_id=(message.get("id") if isinstance(message, dict) else getattr(message, "id", None)), - thread_run_id=(message.get("run_id") if isinstance(message, dict) else getattr(message, "run_id", None)), - message_status=(message.get("status") if isinstance(message, dict) else getattr(message, "status", None)), - incomplete_details=( - message.get("incomplete_details") - if isinstance(message, dict) - else getattr(message, "incomplete_details", None) - ), - usage=usage, - ) - - def _add_message_event( # pylint: disable=too-many-branches,too-many-statements - self, - span, - role: str, - content: Optional[Union[str, dict[str, Any], List[dict[str, Any]]]] = None, - attachments: Any = None, - thread_id: Optional[str] = None, - agent_id: Optional[str] = None, - message_id: Optional[str] = None, - thread_run_id: Optional[str] = None, - message_status: Optional[str] = None, - incomplete_details: Optional[Any] = None, - usage: Optional[Any] = None, - ) -> None: - # TODO document new fields - - content_array: List[Dict[str, Any]] = [] - if _trace_agents_content: - if isinstance(content, List): - for block in content: - if isinstance(block, Dict): - if block.get("type") == "input_text" and "text" in block: - # Use optimized format with consistent "content" field - content_array.append({"type": "text", "content": block["text"]}) - break - elif content: - # Simple text content - content_array.append({"type": "text", "content": content}) - - if attachments: - # Add attachments as separate content items - for attachment in attachments: - attachment_body: Dict[str, Any] = { - "type": "attachment", - "content": {"id": attachment.file_id}, - } - if attachment.tools: - content_dict: Dict[str, Any] = attachment_body["content"] # type: ignore[assignment] - content_dict["tools"] = [self._get_field(tool, "type") for tool in attachment.tools] - content_array.append(attachment_body) - - # Add metadata fields if present - metadata: Dict[str, Any] = {} - if incomplete_details: - metadata["incomplete_details"] = incomplete_details - - # Combine content array with metadata if needed - event_data: Union[Dict[str, Any], List[Dict[str, Any]]] = {} - if metadata: - # When we have metadata, we need to wrap it differently - event_data = metadata - if content_array: - event_data["content"] = content_array - else: - # No metadata, use content array directly as the event data - event_data = content_array if isinstance(content_array, list) else {} - - use_events = _get_use_message_events() - - if use_events: - # Use events for message tracing - attributes = self._create_event_attributes( - thread_id=thread_id, - agent_id=agent_id, - thread_run_id=thread_run_id, - message_id=message_id, - message_status=message_status, - usage=usage, - ) - # Store as JSON - either array or object depending on metadata - if not metadata and content_array: - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - else: - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_data, ensure_ascii=False) - - event_name = None - if role == "user": - event_name = "gen_ai.input.message" - elif role == "system": - event_name = "gen_ai.system_instruction" - else: - event_name = "gen_ai.input.message" - - span.span_instance.add_event(name=event_name, attributes=attributes) - else: - # Use attributes for message tracing - # Prepare message content as JSON - message_json = json.dumps( - [{"role": role, "parts": content_array}] if content_array else [{"role": role}], ensure_ascii=False - ) - - # Determine which attribute to use based on role - if role == "user": - attribute_name = GEN_AI_INPUT_MESSAGES - elif role == "assistant": - attribute_name = GEN_AI_OUTPUT_MESSAGES - else: - # Default to input messages for other roles (including system) - attribute_name = GEN_AI_INPUT_MESSAGES - - # Set the attribute on the span - if span and span.span_instance.is_recording: - span.add_attribute(attribute_name, message_json) - - def _get_field(self, obj: Any, field: str) -> Any: - if not obj: - return None - - if isinstance(obj, dict): - return obj.get(field, None) - - return getattr(obj, field, None) - - def _add_instructions_event( - self, - span: "AbstractSpan", - instructions: Optional[str], - additional_instructions: Optional[str], - agent_id: Optional[str] = None, - thread_id: Optional[str] = None, - response_schema: Optional[Any] = None, - ) -> None: - # Early return if no instructions AND no response schema to trace - if not instructions and response_schema is None: - return - - content_array: List[Dict[str, Any]] = [] - - # Add instructions if provided - if instructions: - if _trace_agents_content: - # Combine instructions if both exist - if additional_instructions: - combined_text = f"{instructions} {additional_instructions}" - else: - combined_text = instructions - - # Use optimized format with consistent "content" field - content_array.append({"type": "text", "content": combined_text}) - else: - # Content recording disabled, but indicate that text instructions exist - content_array.append({"type": "text"}) - - # Add response schema if provided - if response_schema is not None: - if _trace_agents_content: - # Convert schema to JSON string if it's a dict/object - if isinstance(response_schema, dict): - schema_str = json.dumps(response_schema, ensure_ascii=False) - elif hasattr(response_schema, "as_dict"): - # Handle model objects that have as_dict() method (e.g., ResponseFormatJsonSchemaSchema) - schema_dict = response_schema.as_dict() - schema_str = json.dumps(schema_dict, ensure_ascii=False) - # TODO: is this 'elif' still needed, not that we added the above? - elif hasattr(response_schema, "__dict__"): - # Handle model objects by converting to dict first - schema_dict = {k: v for k, v in response_schema.__dict__.items() if not k.startswith("_")} - schema_str = json.dumps(schema_dict, ensure_ascii=False) - else: - schema_str = str(response_schema) - - content_array.append({"type": "response_schema", "content": schema_str}) - else: - # Content recording disabled, but indicate that response schema exists - content_array.append({"type": "response_schema"}) - - use_events = _get_use_message_events() - - if use_events: - # Use events for instructions tracing - attributes = self._create_event_attributes(agent_id=agent_id, thread_id=thread_id) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - span.span_instance.add_event(name=GEN_AI_SYSTEM_INSTRUCTION_EVENT, attributes=attributes) - else: - # Use attributes for instructions tracing - # System instructions format: array of content objects without role/parts wrapper - message_json = json.dumps(content_array, ensure_ascii=False) - if span and span.span_instance.is_recording: - span.add_attribute(GEN_AI_SYSTEM_MESSAGE, message_json) - - def _status_to_string(self, status: Any) -> str: - return status.value if hasattr(status, "value") else status - - @staticmethod - def agent_api_response_to_str(response_format: Any) -> Optional[str]: - """ - Convert response_format to string. - - :param response_format: The response format. - :type response_format: Any - :returns: string for the response_format. - :rtype: Optional[str] - :raises: Value error if response_format is not a supported type. - """ - if isinstance(response_format, str) or response_format is None: - return response_format - raise ValueError(f"Unknown response format {type(response_format)}") - - def start_create_agent_span( # pylint: disable=too-many-locals - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - model: Optional[str] = None, - name: Optional[str] = None, - description: Optional[str] = None, - instructions: Optional[str] = None, - _tools: Optional[List[Tool]] = None, - _tool_resources: Optional[Any] = None, # TODO: Used to be: _tool_resources: Optional[ItemResource] = None, - # _toolset: Optional["ToolSet"] = None, - temperature: Optional[float] = None, - top_p: Optional[float] = None, - response_format: Optional[Any] = None, - reasoning_effort: Optional[str] = None, - reasoning_summary: Optional[str] = None, - text: Optional[Any] = None, # pylint: disable=unused-argument - structured_inputs: Optional[Any] = None, - agent_type: Optional[str] = None, - workflow_yaml: Optional[str] = None, - hosted_cpu: Optional[str] = None, - hosted_memory: Optional[str] = None, - hosted_image: Optional[str] = None, - hosted_protocol: Optional[str] = None, - hosted_protocol_version: Optional[str] = None, - ) -> "Optional[AbstractSpan]": - span = start_span( - OperationName.CREATE_AGENT, - server_address=server_address, - port=port, - span_name=f"{OperationName.CREATE_AGENT.value} {name}", - model=model, - temperature=temperature, - top_p=top_p, - response_format=_AIAgentsInstrumentorPreview.agent_api_response_to_str(response_format), - reasoning_effort=reasoning_effort, - reasoning_summary=reasoning_summary, - structured_inputs=(str(structured_inputs) if structured_inputs is not None else None), - ) - if span and span.span_instance.is_recording: - span.add_attribute(GEN_AI_OPERATION_NAME, OperationName.CREATE_AGENT.value) - if name: - span.add_attribute(GEN_AI_AGENT_NAME, name) - if description: - span.add_attribute(GEN_AI_AGENT_DESCRIPTION, description) - if agent_type: - span.add_attribute(GEN_AI_AGENT_TYPE, agent_type) - - # Add hosted agent specific attributes - if hosted_cpu: - span.add_attribute(GEN_AI_AGENT_HOSTED_CPU, hosted_cpu) - if hosted_memory: - span.add_attribute(GEN_AI_AGENT_HOSTED_MEMORY, hosted_memory) - if hosted_image: - span.add_attribute(GEN_AI_AGENT_HOSTED_IMAGE, hosted_image) - if hosted_protocol: - span.add_attribute(GEN_AI_AGENT_HOSTED_PROTOCOL, hosted_protocol) - if hosted_protocol_version: - span.add_attribute(GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION, hosted_protocol_version) - - # Extract response schema from text parameter if available - response_schema = None - if response_format and text: - # Extract schema from text.format.schema if available - if hasattr(text, "format"): - format_info = getattr(text, "format", None) - if format_info and hasattr(format_info, "schema"): - response_schema = getattr(format_info, "schema", None) - elif isinstance(text, dict): - format_info = text.get("format") - if format_info and isinstance(format_info, dict): - response_schema = format_info.get("schema") - - # Add instructions event (if instructions exist) - self._add_instructions_event(span, instructions, None, response_schema=response_schema) - - # Add workflow event if workflow type agent (always add event, but only include YAML content if content recording enabled) - if workflow_yaml is not None: - # Always create event with empty array or workflow content based on recording setting - if _trace_agents_content: - # Include actual workflow YAML when content recording is enabled - event_body: List[Dict[str, Any]] = [{"type": "workflow", "content": workflow_yaml}] - else: - # Empty array when content recording is disabled (agent type already indicates it's a workflow) - event_body = [] - attributes = self._create_event_attributes() - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False) - span.span_instance.add_event(name="gen_ai.agent.workflow", attributes=attributes) - - return span - - def start_create_thread_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - messages: Optional[List[Dict[str, str]]] = None, - # _tool_resources: Optional["ToolResources"] = None, - ) -> "Optional[AbstractSpan]": - span = start_span(OperationName.CREATE_THREAD, server_address=server_address, port=port) - if span and span.span_instance.is_recording: - for message in messages or []: - self.add_thread_message_event(span, message) - - return span - - def get_server_address_from_arg(self, arg: Any) -> Optional[Tuple[str, Optional[int]]]: - """ - Extracts the base endpoint and port from the provided arguments _config.endpoint attribute, if that exists. - - :param arg: The argument from which the server address is to be extracted. - :type arg: Any - :return: A tuple of (base endpoint, port) or None if endpoint is not found. - :rtype: Optional[Tuple[str, Optional[int]]] - """ - if hasattr(arg, "_config") and hasattr( - arg._config, # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - "endpoint", - ): - endpoint = ( - arg._config.endpoint # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - ) - parsed_url = urlparse(endpoint) - return f"{parsed_url.scheme}://{parsed_url.netloc}", parsed_url.port - return None - - def _create_agent_span_from_parameters( - self, *args, **kwargs - ): # pylint: disable=too-many-statements,too-many-locals,too-many-branches,docstring-missing-param - """Extract parameters and create span for create_agent tracing.""" - server_address_info = self.get_server_address_from_arg(args[0]) - server_address = server_address_info[0] if server_address_info else None - port = server_address_info[1] if server_address_info else None - - # Extract parameters from the new nested structure - agent_name = kwargs.get("agent_name") - definition = kwargs.get("definition", {}) - if definition is None: - body = kwargs.get("body", {}) - definition = body.get("definition", {}) - - # Extract parameters from definition - model = definition.get("model") - instructions = definition.get("instructions") - temperature = definition.get("temperature") - top_p = definition.get("top_p") - tools = definition.get("tools") - reasoning = definition.get("reasoning") - text = definition.get("text") - structured_inputs = None - description = definition.get("description") - tool_resources = definition.get("tool_resources") - workflow_yaml = definition.get("workflow") # Extract workflow YAML for workflow agents - # toolset = definition.get("toolset") - - # Extract hosted agent specific attributes - hosted_cpu = definition.get("cpu") - hosted_memory = definition.get("memory") - hosted_image = definition.get("image") - hosted_protocol = None - hosted_protocol_version = None - container_protocol_versions = definition.get("container_protocol_versions") - if container_protocol_versions and len(container_protocol_versions) > 0: - # Extract protocol and version from first entry - protocol_record = container_protocol_versions[0] - if hasattr(protocol_record, "protocol"): - hosted_protocol = getattr(protocol_record, "protocol", None) - elif isinstance(protocol_record, dict): - hosted_protocol = protocol_record.get("protocol") - - if hasattr(protocol_record, "version"): - hosted_protocol_version = getattr(protocol_record, "version", None) - elif isinstance(protocol_record, dict): - hosted_protocol_version = protocol_record.get("version") - - # Determine agent type from definition - # Check for hosted agent first (most specific - has container/image configuration) - agent_type = None - if hosted_image or hosted_cpu or hosted_memory: - agent_type = AGENT_TYPE_HOSTED - elif workflow_yaml: - agent_type = AGENT_TYPE_WORKFLOW - elif instructions or model: - # Prompt agent - identified by having instructions and/or a model. - # Note: An agent with only a model (no instructions) is treated as a prompt agent, - # though this is uncommon. Typically prompt agents have both model and instructions. - agent_type = AGENT_TYPE_PROMPT - else: - # Unknown type - set to "unknown" to indicate we couldn't determine it - agent_type = AGENT_TYPE_UNKNOWN - - # Extract reasoning effort and summary from reasoning if available - reasoning_effort = None - reasoning_summary = None - if reasoning: - # Handle different types of reasoning objects - if hasattr(reasoning, "effort") and hasattr(reasoning, "summary"): - # Azure OpenAI Reasoning model object - reasoning_effort = getattr(reasoning, "effort", None) - reasoning_summary = getattr(reasoning, "summary", None) - elif isinstance(reasoning, dict): - # Dictionary format - reasoning_effort = reasoning.get("effort") - reasoning_summary = reasoning.get("summary") - elif isinstance(reasoning, str): - # Try to parse as JSON if it's a string - try: - reasoning_dict = json.loads(reasoning) - if isinstance(reasoning_dict, dict): - reasoning_effort = reasoning_dict.get("effort") - reasoning_summary = reasoning_dict.get("summary") - except (json.JSONDecodeError, ValueError): - # If parsing fails, treat the whole string as effort - reasoning_effort = reasoning - - # Extract response format from text.format if available - response_format = None - if text: - # Handle different types of text objects - if hasattr(text, "format"): - # Azure AI Agents PromptAgentDefinitionTextOptions model object - format_info = getattr(text, "format", None) - if format_info: - if hasattr(format_info, "type"): - # Format is also a model object - response_format = getattr(format_info, "type", None) - elif isinstance(format_info, dict): - # Format is a dictionary - response_format = format_info.get("type") - elif isinstance(text, dict): - # Dictionary format - format_info = text.get("format") - if format_info and isinstance(format_info, dict): - format_type = format_info.get("type") - if format_type: - response_format = format_type - elif isinstance(text, str): - # Try to parse as JSON if it's a string - try: - text_dict = json.loads(text) - if isinstance(text_dict, dict): - format_info = text_dict.get("format") - if format_info and isinstance(format_info, dict): - format_type = format_info.get("type") - if format_type: - response_format = format_type - except (json.JSONDecodeError, ValueError): - # If parsing fails, ignore - pass - - # Create and return the span - return self.start_create_agent_span( - server_address=server_address, - port=port, - name=agent_name, - model=model, - description=description, - instructions=instructions, - _tools=tools, - _tool_resources=tool_resources, - temperature=temperature, - top_p=top_p, - response_format=response_format, - reasoning_effort=reasoning_effort, - reasoning_summary=reasoning_summary, - text=text, - structured_inputs=structured_inputs, - agent_type=agent_type, - workflow_yaml=workflow_yaml, - hosted_cpu=hosted_cpu, - hosted_memory=hosted_memory, - hosted_image=hosted_image, - hosted_protocol=hosted_protocol, - hosted_protocol_version=hosted_protocol_version, - ) - - def trace_create_agent(self, function, *args, **kwargs): - span = self._create_agent_span_from_parameters(*args, **kwargs) - - if span is None: - return function(*args, **kwargs) - - with span: - try: - result = function(*args, **kwargs) - span.add_attribute(GEN_AI_AGENT_ID, result.id) - span.add_attribute(GEN_AI_AGENT_VERSION, result.version) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - async def trace_create_agent_async(self, function, *args, **kwargs): - span = self._create_agent_span_from_parameters(*args, **kwargs) - - if span is None: - return await function(*args, **kwargs) - - with span: - try: - result = await function(*args, **kwargs) - span.add_attribute(GEN_AI_AGENT_ID, result.id) - span.add_attribute(GEN_AI_AGENT_VERSION, result.version) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - def _create_thread_span_from_parameters(self, *args, **kwargs): - """Extract parameters, process messages, and create span for create_thread tracing.""" - server_address_info = self.get_server_address_from_arg(args[0]) - server_address = server_address_info[0] if server_address_info else None - port = server_address_info[1] if server_address_info else None - messages = kwargs.get("messages") - items = kwargs.get("items") - if items is None: - body = kwargs.get("body") - if isinstance(body, dict): - items = body.get("items") - - # Process items if available to extract content from generators - processed_messages = messages - if items: - processed_messages = [] - for item in items: - # Handle model objects like ResponsesUserMessageItemParam, ResponsesSystemMessageItemParam - if hasattr(item, "__dict__"): - final_content = str(getattr(item, "content", "")) - # Create message structure for telemetry - role = getattr(item, "role", "unknown") - processed_messages.append({"role": role, "content": final_content}) - else: - # Handle dict items or simple string items - if isinstance(item, dict): - processed_messages.append(item) - else: - # Handle simple string items - processed_messages.append({"role": "unknown", "content": str(item)}) - - # Create and return the span - return self.start_create_thread_span(server_address=server_address, port=port, messages=processed_messages) - - def trace_create_thread(self, function, *args, **kwargs): - span = self._create_thread_span_from_parameters(*args, **kwargs) - - if span is None: - return function(*args, **kwargs) - - with span: - try: - result = function(*args, **kwargs) - span.add_attribute(GEN_AI_THREAD_ID, result.get("id")) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - async def trace_create_thread_async(self, function, *args, **kwargs): - span = self._create_thread_span_from_parameters(*args, **kwargs) - - if span is None: - return await function(*args, **kwargs) - - with span: - try: - result = await function(*args, **kwargs) - span.add_attribute(GEN_AI_THREAD_ID, result.get("id")) - except Exception as exc: - self.record_error(span, exc) - raise - - return result - - def trace_list_messages_async(self, function, *args, **kwargs): - """Placeholder method for list messages async tracing. - - The full instrumentation infrastructure for list operations - is not yet implemented, so we simply call the original function. - - :param function: The original function to be called. - :type function: Callable - :param args: Positional arguments passed to the original function. - :type args: tuple - :param kwargs: Keyword arguments passed to the original function. - :type kwargs: dict - :return: The result of calling the original function. - :rtype: Any - """ - return function(*args, **kwargs) - - def trace_list_run_steps_async(self, function, *args, **kwargs): - """Placeholder method for list run steps async tracing. - - The full instrumentation infrastructure for list operations - is not yet implemented, so we simply call the original function. - - :param function: The original function to be called. - :type function: Callable - :param args: Positional arguments passed to the original function. - :type args: tuple - :param kwargs: Keyword arguments passed to the original function. - :type kwargs: dict - :return: The result of calling the original function. - :rtype: Any - """ - return function(*args, **kwargs) - - def _trace_sync_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.AGENTS, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to a synchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - def inner(*args, **kwargs): # pylint: disable=R0911 - span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 - if span_impl_type is None: - return function(*args, **kwargs) - - class_function_name = function.__qualname__ - - if class_function_name.endswith(".create_version") and ("AgentsOperations" in class_function_name): - kwargs.setdefault("merge_span", True) - return self.trace_create_agent(function, *args, **kwargs) - # if class_function_name.startswith("ConversationsOperations.create"): - # kwargs.setdefault("merge_span", True) - # return self.trace_create_thread(function, *args, **kwargs) - return function(*args, **kwargs) # Ensure all paths return - - return inner - - def _trace_async_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.AGENTS, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to an asynchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - async def inner(*args, **kwargs): # pylint: disable=R0911 - span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 - if span_impl_type is None: - return await function(*args, **kwargs) - - class_function_name = function.__qualname__ - - if class_function_name.endswith(".create_version") and ("AgentsOperations" in class_function_name): - kwargs.setdefault("merge_span", True) - return await self.trace_create_agent_async(function, *args, **kwargs) - # if class_function_name.startswith("ConversationOperations.create"): - # kwargs.setdefault("merge_span", True) - # return await self.trace_create_thread_async(function, *args, **kwargs) - return await function(*args, **kwargs) # Ensure all paths return - - return inner - - def _trace_async_list_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.AGENTS, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to an asynchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. - Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.AGENTS. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - def inner(*args, **kwargs): # pylint: disable=R0911 - span_impl_type = settings.tracing_implementation() # pylint: disable=E1102 - if span_impl_type is None: - return function(*args, **kwargs) - - class_function_name = function.__qualname__ - if class_function_name.startswith("MessagesOperations.list"): - kwargs.setdefault("merge_span", True) - return self.trace_list_messages_async(function, *args, **kwargs) - if class_function_name.startswith("RunStepsOperations.list"): - kwargs.setdefault("merge_span", True) - return self.trace_list_run_steps_async(function, *args, **kwargs) - # Handle the default case (if the function name does not match) - return None # Ensure all paths return - - return inner - - def _inject_async(self, f, _trace_type, _name): - if _name.startswith("list"): - wrapper_fun = self._trace_async_list_function(f) - else: - wrapper_fun = self._trace_async_function(f) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _inject_sync(self, f, _trace_type, _name): - wrapper_fun = self._trace_sync_function(f) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _agents_apis(self): - sync_apis = ( - ( - "azure.ai.projects.operations", - "AgentsOperations", - "create_version", - TraceType.AGENTS, - "create_version", - ), - # ( - # "azure.ai.agents.operations", - # "ConversationsOperations", - # "create", - # TraceType.AGENTS, - # "create", - # ), - ) - async_apis = ( - ( - "azure.ai.projects.aio.operations", - "AgentsOperations", - "create_version", - TraceType.AGENTS, - "create_version", - ), - # ( - # "azure.ai.agents.aio.operations", - # "ConversationsOperations", - # "create", - # TraceType.AGENTS, - # "create", - # ), - ) - return sync_apis, async_apis - - def _project_apis(self): - """Define AIProjectClient APIs to instrument for trace propagation. - - :return: A tuple containing sync and async API tuples. - :rtype: Tuple[Tuple, Tuple] - """ - sync_apis = ( - ( - "azure.ai.projects", - "AIProjectClient", - "get_openai_client", - TraceType.PROJECT, - "get_openai_client", - ), - ) - async_apis = ( - ( - "azure.ai.projects.aio", - "AIProjectClient", - "get_openai_client", - TraceType.PROJECT, - "get_openai_client", - ), - ) - return sync_apis, async_apis - - def _inject_openai_client(self, f, _trace_type, _name): - """Injector for get_openai_client that enables trace context propagation if opted in. - - :return: The wrapped function with trace context propagation enabled. - :rtype: Callable - """ - - @functools.wraps(f) - def wrapper(*args, **kwargs): - openai_client = f(*args, **kwargs) - if _trace_context_propagation_enabled: - _enable_trace_propagation_for_openai_client(openai_client) - return openai_client - - wrapper._original = f # type: ignore # pylint: disable=protected-access - return wrapper - - def _agents_api_list(self): - sync_apis, async_apis = self._agents_apis() - yield sync_apis, self._inject_sync - yield async_apis, self._inject_async - - def _project_api_list(self): - """Generate project API list with custom injector. - - :return: A generator yielding API tuples with injectors. - :rtype: Generator - """ - sync_apis, async_apis = self._project_apis() - yield sync_apis, self._inject_openai_client - yield async_apis, self._inject_openai_client - - def _generate_api_and_injector(self, apis): - for api, injector in apis: - for module_name, class_name, method_name, trace_type, name in api: - try: - module = importlib.import_module(module_name) - api = getattr(module, class_name) - if hasattr(api, method_name): - # The function list is sync in both sync and async classes. - yield api, method_name, trace_type, injector, name - except AttributeError as e: - # Log the attribute exception with the missing class information - logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "AttributeError: The module '%s' does not have the class '%s'. %s", - module_name, - class_name, - str(e), - ) - except Exception as e: # pylint: disable=broad-except - # Log other exceptions as a warning, as we are not sure what they might be - logger.warning( # pylint: disable=do-not-log-exceptions-if-not-debug - "An unexpected error occurred: '%s'", str(e) - ) - - def _available_projects_apis_and_injectors(self): - """ - Generates a sequence of tuples containing Agents and Project API classes, method names, and - corresponding injector functions. - - :return: A generator yielding tuples. - :rtype: tuple - """ - yield from self._generate_api_and_injector(self._agents_api_list()) - yield from self._generate_api_and_injector(self._project_api_list()) - - def _instrument_projects( - self, - enable_content_tracing: bool = False, - enable_trace_context_propagation: bool = False, - enable_baggage_propagation: bool = False, - ): - """This function modifies the methods of the Projects API classes to - inject logic before calling the original methods. - The original methods are stored as _original attributes of the methods. - - :param enable_content_tracing: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_tracing: bool - :param enable_trace_context_propagation: Whether to enable automatic trace context propagation. - :type enable_trace_context_propagation: bool - :param enable_baggage_propagation: Whether to include baggage in trace context propagation. - :type enable_baggage_propagation: bool - """ - # pylint: disable=W0603 - global _projects_traces_enabled - global _trace_agents_content - global _trace_context_propagation_enabled - global _trace_context_baggage_propagation_enabled - if _projects_traces_enabled: - raise RuntimeError("Traces already started for AI Agents") - - _projects_traces_enabled = True - _trace_agents_content = enable_content_tracing - _trace_context_propagation_enabled = enable_trace_context_propagation - _trace_context_baggage_propagation_enabled = enable_baggage_propagation - for ( - api, - method, - trace_type, - injector, - name, - ) in self._available_projects_apis_and_injectors(): - # Check if the method of the api class has already been modified - if not hasattr(getattr(api, method), "_original"): - setattr(api, method, injector(getattr(api, method), trace_type, name)) - - def _uninstrument_projects(self): - """This function restores the original methods of the Projects API classes - by assigning them back from the _original attributes of the modified methods. - """ - # pylint: disable=W0603 - global _projects_traces_enabled - global _trace_agents_content - global _trace_context_propagation_enabled - global _trace_context_baggage_propagation_enabled - _trace_agents_content = False - _trace_context_propagation_enabled = False - _trace_context_baggage_propagation_enabled = False - for api, method, _, _, _ in self._available_projects_apis_and_injectors(): - if hasattr(getattr(api, method), "_original"): - setattr(api, method, getattr(getattr(api, method), "_original")) - - _projects_traces_enabled = False - - def _is_instrumented(self): - """This function returns True if Projects API has already been instrumented - for tracing and False if it has not been instrumented. - - :return: A value indicating whether the Projects API is currently instrumented or not. - :rtype: bool - """ - return _projects_traces_enabled - - def _set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - """This function sets the content recording value. - - :param enable_content_recording: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_recording: bool - """ - global _trace_agents_content # pylint: disable=W0603 - _trace_agents_content = enable_content_recording - - def _is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content tracing is enabled. - :rtype bool - """ - return _trace_agents_content - - def _set_enable_trace_context_propagation(self, enable_trace_context_propagation: bool = False) -> None: - """This function sets the trace context propagation value. - - :param enable_trace_context_propagation: Indicates whether automatic trace context propagation should be enabled. - :type enable_trace_context_propagation: bool - """ - global _trace_context_propagation_enabled # pylint: disable=W0603 - _trace_context_propagation_enabled = enable_trace_context_propagation - - def _set_enable_baggage_propagation(self, enable_baggage_propagation: bool = False) -> None: - """This function sets the baggage propagation value. - - :param enable_baggage_propagation: Indicates whether baggage should be included in trace context propagation. - :type enable_baggage_propagation: bool - """ - global _trace_context_baggage_propagation_enabled # pylint: disable=W0603 - _trace_context_baggage_propagation_enabled = enable_baggage_propagation - - def record_error(self, span, exc): - # Set the span status to error - if isinstance(span.span_instance, Span): # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status( - StatusCode.ERROR, # pyright: ignore [reportPossiblyUnboundVariable] - description=str(exc), - ) - module = getattr(exc, "__module__", "") - module = module if module != "builtins" else "" - error_type = f"{module}.{type(exc).__name__}" if module else type(exc).__name__ - self._set_attributes(span, ("error.type", error_type)) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py deleted file mode 100644 index 95cb28183b35..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_responses_instrumentor.py +++ /dev/null @@ -1,4883 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression,too-many-lines -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -# pyright: reportPossiblyUnboundVariable=false -# pylint: disable=too-many-lines,line-too-long,useless-suppression,too-many-nested-blocks,docstring-missing-param,docstring-should-be-keyword,docstring-missing-return,docstring-missing-rtype,broad-exception-caught,logging-fstring-interpolation,unused-variable,unused-argument,protected-access,global-variable-not-assigned,global-statement -# Pylint disables are appropriate for this internal instrumentation class because: -# - Extensive documentation isn't required for internal methods (docstring-missing-*) -# - Broad exception catching is often necessary for telemetry (shouldn't break user code) -# - Protected access is needed for instrumentation to hook into client internals -# - Some unused variables/arguments exist for API compatibility and future extensibility -# - Global variables are used for metrics state management across instances -# - Line length and complexity limits are relaxed for instrumentation code -import functools -import json -import logging -import os -import time -from enum import Enum -from typing import Any, Callable, Dict, List, Optional, Tuple, TYPE_CHECKING -from urllib.parse import urlparse -from azure.core import CaseInsensitiveEnumMeta # type: ignore -from azure.core.tracing import AbstractSpan -from ._utils import ( - ERROR_TYPE, - GEN_AI_AGENT_ID, - GEN_AI_AGENT_NAME, - GEN_AI_ASSISTANT_MESSAGE_EVENT, - GEN_AI_CLIENT_OPERATION_DURATION, - GEN_AI_CLIENT_TOKEN_USAGE, - GEN_AI_CONVERSATION_ID, - GEN_AI_CONVERSATION_ITEM_EVENT, - GEN_AI_CONVERSATION_ITEM_ID, - GEN_AI_EVENT_CONTENT, - GEN_AI_INPUT_MESSAGES, - GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, - GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT, - GEN_AI_OPERATION_NAME, - GEN_AI_OUTPUT_MESSAGES, - GEN_AI_PROVIDER_NAME, - GEN_AI_REQUEST_MODEL, - GEN_AI_REQUEST_TOOLS, - GEN_AI_RESPONSE_FINISH_REASONS, - GEN_AI_RESPONSE_ID, - GEN_AI_RESPONSE_MODEL, - GEN_AI_TOKEN_TYPE, - GEN_AI_TOOL_MESSAGE_EVENT, - GEN_AI_USAGE_INPUT_TOKENS, - GEN_AI_USAGE_OUTPUT_TOKENS, - GEN_AI_USER_MESSAGE_EVENT, - GEN_AI_WORKFLOW_ACTION_EVENT, - OPERATION_NAME_CHAT, - OPERATION_NAME_INVOKE_AGENT, - OperationName, - SERVER_ADDRESS, - SERVER_PORT, - SPAN_NAME_CHAT, - SPAN_NAME_INVOKE_AGENT, - _get_use_message_events, - _get_use_simple_tool_format, - start_span, - RESPONSES_PROVIDER, -) - -_Unset: Any = object() - -logger = logging.getLogger(__name__) - -try: # pylint: disable=unused-import - # pylint: disable = no-name-in-module - from opentelemetry.trace import StatusCode - from opentelemetry.metrics import get_meter - - _tracing_library_available = True -except ModuleNotFoundError: - _tracing_library_available = False - -if TYPE_CHECKING: - pass - -__all__ = [ - "ResponsesInstrumentor", -] - -_responses_traces_enabled: bool = False -_trace_responses_content: bool = False -_trace_binary_data: bool = False - -# Metrics instruments -_operation_duration_histogram = None -_token_usage_histogram = None - - -class TraceType(str, Enum, metaclass=CaseInsensitiveEnumMeta): # pylint: disable=C4747 - """An enumeration class to represent different types of traces.""" - - RESPONSES = "Responses" - CONVERSATIONS = "Conversations" - - -class ResponsesInstrumentor: - """ - A class for managing the trace instrumentation of OpenAI Responses and Conversations APIs. - - This class allows enabling or disabling tracing for OpenAI Responses and Conversations API calls - and provides functionality to check whether instrumentation is active. - """ - - def __init__(self) -> None: - if not _tracing_library_available: - logger.warning( - "OpenTelemetry is not available. " - "Please install opentelemetry-api and opentelemetry-sdk to enable tracing." - ) - # We could support different semantic convention versions from the same library - # and have a parameter that specifies the version to use. - self._impl = _ResponsesInstrumentorPreview() - - def instrument(self, enable_content_recording: Optional[bool] = None) -> None: - """ - Enable trace instrumentation for OpenAI Responses and Conversations APIs. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - """ - self._impl.instrument(enable_content_recording) - - def uninstrument(self) -> None: - """ - Remove trace instrumentation for OpenAI Responses and Conversations APIs. - - This method removes any active instrumentation, stopping the tracing - of OpenAI Responses and Conversations API methods. - """ - self._impl.uninstrument() - - def is_instrumented(self) -> bool: - """ - Check if trace instrumentation for OpenAI Responses and Conversations APIs is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._impl.is_instrumented() - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content recording is enabled. - :rtype: bool - """ - return self._impl.is_content_recording_enabled() - - def is_binary_data_enabled(self) -> bool: - """This function gets the binary data tracing value. - - :return: A bool value indicating whether binary data tracing is enabled. - :rtype: bool - """ - return self._impl.is_binary_data_enabled() - - def is_simple_tool_format_enabled(self) -> bool: - """This function gets the simple tool format value. - - When enabled, function tool calls use a simplified OTEL-compliant format: - tool_call: {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} - tool_call_response: {"type": "tool_call_response", "id": "...", "result": "..."} - - :return: A bool value indicating whether simple tool format is enabled. - :rtype: bool - """ - return _get_use_simple_tool_format() - - -class _ResponsesInstrumentorPreview: # pylint: disable=too-many-instance-attributes,too-many-statements,too-many-public-methods - """ - A class for managing the trace instrumentation of OpenAI Responses API. - - This class allows enabling or disabling tracing for OpenAI Responses API calls - and provides functionality to check whether instrumentation is active. - """ - - def _str_to_bool(self, s): - if s is None: - return False - return str(s).lower() == "true" - - def _is_instrumentation_enabled(self) -> bool: - """Check if instrumentation is enabled via environment variable. - - Returns True if AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API is not set or is "true" (case insensitive). - Returns False if the environment variable is set to any other value. - """ - env_value = os.environ.get("AZURE_TRACING_GEN_AI_INSTRUMENT_RESPONSES_API") - if env_value is None: - return True # Default to enabled if not specified - return str(env_value).lower() == "true" - - def _initialize_metrics(self): - """Initialize OpenTelemetry metrics instruments.""" - global _operation_duration_histogram, _token_usage_histogram # pylint: disable=global-statement - - if not _tracing_library_available: - return - - try: - meter = get_meter(__name__) # pyright: ignore [reportPossiblyUnboundVariable] - - # Operation duration histogram - _operation_duration_histogram = meter.create_histogram( - name=GEN_AI_CLIENT_OPERATION_DURATION, - description="Duration of GenAI operations", - unit="s", - ) - - # Token usage histogram - _token_usage_histogram = meter.create_histogram( - name=GEN_AI_CLIENT_TOKEN_USAGE, - description="Token usage for GenAI operations", - unit="token", - ) - - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to initialize metrics: %s", e) - - def _record_operation_duration( - self, - duration: float, - operation_name: str, - server_address: Optional[str] = None, - port: Optional[int] = None, - model: Optional[str] = None, - error_type: Optional[str] = None, - ): - """Record operation duration metrics.""" - global _operation_duration_histogram # pylint: disable=global-variable-not-assigned - - if not _operation_duration_histogram: - return - - attributes = { - GEN_AI_OPERATION_NAME: operation_name, - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - } - - if server_address: - attributes[SERVER_ADDRESS] = server_address - if port: - attributes[SERVER_PORT] = str(port) - if model: - attributes[GEN_AI_REQUEST_MODEL] = model - if error_type: - attributes[ERROR_TYPE] = error_type - - try: - _operation_duration_histogram.record(duration, attributes) - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to record operation duration: %s", e) - - def _record_token_usage( - self, - token_count: int, - token_type: str, - operation_name: str, - server_address: Optional[str] = None, - model: Optional[str] = None, - ): - """Record token usage metrics.""" - global _token_usage_histogram # pylint: disable=global-variable-not-assigned - - if not _token_usage_histogram: - return - - attributes = { - GEN_AI_OPERATION_NAME: operation_name, - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - GEN_AI_TOKEN_TYPE: token_type, - } - - if server_address: - attributes[SERVER_ADDRESS] = server_address - if model: - attributes[GEN_AI_REQUEST_MODEL] = model - - try: - _token_usage_histogram.record(token_count, attributes) - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to record token usage: %s", e) - - def _record_token_metrics_from_response( - self, - response: Any, - operation_name: str, - server_address: Optional[str] = None, - model: Optional[str] = None, - ): - """Extract and record token usage metrics from response.""" - try: - if hasattr(response, "usage"): - usage = response.usage - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self._record_token_usage( - usage.prompt_tokens, - "input", - operation_name, - server_address, - model, - ) - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self._record_token_usage( - usage.completion_tokens, - "completion", - operation_name, - server_address, - model, - ) - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to extract token metrics from response: %s", e) - - def _record_metrics( # pylint: disable=docstring-missing-type - self, - operation_type: str, - duration: float, - result: Any = None, - span_attributes: Optional[Dict[str, Any]] = None, - error_type: Optional[str] = None, - ): - """ - Record comprehensive metrics for different API operation types. - - :param operation_type: Type of operation ("responses", "conversation", "conversation_items") - :param duration: Operation duration in seconds - :param result: API response object for extracting response-specific attributes - :param span_attributes: Dictionary of span attributes to extract relevant metrics from - :param error_type: Error type if an error occurred - """ - try: - # Build base attributes - always included - if operation_type == "responses": - operation_name = "responses" - elif operation_type == "conversation": - operation_name = "create_conversation" - elif operation_type == "conversation_items": - operation_name = "list_conversation_items" - else: - operation_name = operation_type - - # Extract relevant attributes from span_attributes if provided - server_address = None - server_port = None - request_model = None - - if span_attributes: - server_address = span_attributes.get(SERVER_ADDRESS) - server_port = span_attributes.get(SERVER_PORT) - request_model = span_attributes.get(GEN_AI_REQUEST_MODEL) - - # Extract response-specific attributes from result if provided - response_model = None - - if result: - response_model = getattr(result, "model", None) - # service_tier = getattr(result, "service_tier", None) # Unused - - # Use response model if available, otherwise fall back to request model - model_for_metrics = response_model or request_model - - # Record operation duration with relevant attributes - self._record_operation_duration( - duration=duration, - operation_name=operation_name, - server_address=server_address, - port=server_port, - model=model_for_metrics, - error_type=error_type, - ) - - # Record token usage metrics if result has usage information - if result and hasattr(result, "usage"): - usage = result.usage - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self._record_token_usage( - token_count=usage.prompt_tokens, - token_type="input", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self._record_token_usage( - token_count=usage.completion_tokens, - token_type="completion", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - # Handle Responses API specific token fields - if hasattr(usage, "input_tokens") and usage.input_tokens: - self._record_token_usage( - token_count=usage.input_tokens, - token_type="input", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - if hasattr(usage, "output_tokens") and usage.output_tokens: - self._record_token_usage( - token_count=usage.output_tokens, - token_type="completion", - operation_name=operation_name, - server_address=server_address, - model=model_for_metrics, - ) - - except Exception as e: # pylint: disable=broad-exception-caught - logger.debug("Failed to record metrics for %s: %s", operation_type, e) - - def instrument(self, enable_content_recording: Optional[bool] = None): - """ - Enable trace instrumentation for OpenAI Responses API. - - :param enable_content_recording: Whether content recording is enabled as part - of the traces or not. Content in this context refers to chat message content - and function call tool related function names, function parameter names and - values. `True` will enable content recording, `False` will disable it. If no value - is provided, then the value read from environment variable - OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT is used. If the environment - variable is not found, then the value will default to `False`. - Please note that successive calls to instrument will always apply the content - recording value provided with the most recent call to instrument (including - applying the environment variable if no value is provided and defaulting to `False` - if the environment variable is not found), even if instrument was already previously - called without uninstrument being called in between the instrument calls. - :type enable_content_recording: bool, optional - """ - # Check if instrumentation is enabled via environment variable - if not self._is_instrumentation_enabled(): - return # No-op if instrumentation is disabled - - if enable_content_recording is None: - enable_content_recording = self._str_to_bool( - os.environ.get("OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT", "false") - ) - - # Check if binary data tracing is enabled - enable_binary_data = self._str_to_bool(os.environ.get("AZURE_TRACING_GEN_AI_INCLUDE_BINARY_DATA", "false")) - - if not self.is_instrumented(): - self._instrument_responses(enable_content_recording, enable_binary_data) - else: - self.set_enable_content_recording(enable_content_recording) - self.set_enable_binary_data(enable_binary_data) - - def uninstrument(self): - """ - Disable trace instrumentation for OpenAI Responses API. - - This method removes any active instrumentation, stopping the tracing - of OpenAI Responses API calls. - """ - if self.is_instrumented(): - self._uninstrument_responses() - - def is_instrumented(self): - """ - Check if trace instrumentation for OpenAI Responses API is currently enabled. - - :return: True if instrumentation is active, False otherwise. - :rtype: bool - """ - return self._is_instrumented() - - def set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - """This function sets the content recording value. - - :param enable_content_recording: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_recording: bool - """ - self._set_enable_content_recording(enable_content_recording=enable_content_recording) - - def is_content_recording_enabled(self) -> bool: - """This function gets the content recording value. - - :return: A bool value indicating whether content tracing is enabled. - :rtype bool - """ - return self._is_content_recording_enabled() - - def set_enable_binary_data(self, enable_binary_data: bool = False) -> None: - """This function sets the binary data tracing value. - - :param enable_binary_data: Indicates whether tracing of binary data (such as images) should be enabled. - This only takes effect when content recording is also enabled. - :type enable_binary_data: bool - """ - self._set_enable_binary_data(enable_binary_data=enable_binary_data) - - def is_binary_data_enabled(self) -> bool: - """This function gets the binary data tracing value. - - :return: A bool value indicating whether binary data tracing is enabled. - :rtype: bool - """ - return self._is_binary_data_enabled() - - def _set_attributes(self, span: "AbstractSpan", *attrs: Tuple[str, Any]) -> None: - for attr in attrs: - span.add_attribute(attr[0], attr[1]) - - def _set_span_attribute_safe(self, span: "AbstractSpan", key: str, value: Any) -> None: - """Safely set a span attribute only if the value is meaningful.""" - if not span or not span.span_instance.is_recording: - return - - # Only set attribute if value exists and is meaningful - if value is not None and value != "" and value != []: - span.add_attribute(key, value) - - def _parse_url(self, url): - parsed = urlparse(url) - server_address = parsed.hostname - port = parsed.port - return server_address, port - - def _create_event_attributes( - self, - conversation_id: Optional[str] = None, # pylint: disable=unused-argument - message_role: Optional[str] = None, - ) -> Dict[str, Any]: - attrs: Dict[str, Any] = {GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER} - # Removed conversation_id from event attributes as requested - it's redundant - # if conversation_id: - # attrs[GEN_AI_CONVERSATION_ID] = conversation_id - # Commented out - message_role is now included in the event content instead - # if message_role: - # attrs[GEN_AI_MESSAGE_ROLE] = message_role - return attrs - - def _append_to_message_attribute( - self, - span: "AbstractSpan", - attribute_name: str, - new_messages: List[Dict[str, Any]], - ) -> None: - """Helper to append messages to an existing attribute, combining with previous messages.""" - # Get existing attribute value - existing_value = span.span_instance.attributes.get(attribute_name) if span.span_instance.attributes else None - - if existing_value: - # Parse existing JSON array - try: - existing_messages = json.loads(existing_value) - if not isinstance(existing_messages, list): - existing_messages = [] - except (json.JSONDecodeError, TypeError): - existing_messages = [] - - # Append new messages - combined_messages = existing_messages + new_messages - else: - # No existing value, just use new messages - combined_messages = new_messages - - # Set the combined value - combined_json = json.dumps(combined_messages, ensure_ascii=False) - span.add_attribute(attribute_name, combined_json) - - def _add_message_event( - self, - span: "AbstractSpan", - role: str, - content: Optional[str] = None, - conversation_id: Optional[str] = None, - finish_reason: Optional[str] = None, - ) -> None: - """Add a message event or attribute to the span based on configuration.""" - content_array: List[Dict[str, Any]] = [] - - # Always include role and finish_reason, only include actual content if tracing is enabled - if content: - parts = [] - if _trace_responses_content: - # Include actual content when tracing is enabled - parts = [{"type": "text", "content": content}] - else: - # When content recording is off but we know there's content, include type-only structure - parts = [{"type": "text"}] - - # Create role object - role_obj: Dict[str, Any] = {"role": role} - - # Always add parts to show content structure - role_obj["parts"] = parts - - # Add finish_reason for assistant messages if available - if role == "assistant" and finish_reason: - role_obj["finish_reason"] = finish_reason - - content_array.append(role_obj) - - # Serialize the content array to JSON - json_content = json.dumps(content_array, ensure_ascii=False) - - if _get_use_message_events(): - # Original event-based implementation - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role=role, - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json_content - - # Map role to appropriate event name constant - if role == "user": - event_name = GEN_AI_USER_MESSAGE_EVENT - elif role == "assistant": - event_name = GEN_AI_ASSISTANT_MESSAGE_EVENT - else: - # Fallback for any other roles (shouldn't happen in practice) - event_name = f"gen_ai.{role}.message" - - span.span_instance.add_event(name=event_name, attributes=attributes) - else: - # New attribute-based implementation - # Append messages to the appropriate attribute (accumulating multiple messages) - if role in ("user", "tool"): - # User and tool messages go to input.messages - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - elif role == "assistant": - # Assistant messages go to output.messages - self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) - - def _add_tool_message_events( # pylint: disable=too-many-branches - self, - span: "AbstractSpan", - tool_outputs: List[Any], - conversation_id: Optional[str] = None, - ) -> None: - """Add tool message events (tool call outputs) to the span.""" - parts: List[Dict[str, Any]] = [] - - # Always iterate through tool_outputs to build type and id metadata - if tool_outputs: - for output_item in tool_outputs: - try: - tool_output: Dict[str, Any] = {} - - # Get the item type - handle both dict and object attributes - if isinstance(output_item, dict): - item_type = output_item.get("type") - else: - item_type = getattr(output_item, "type", None) - - if not item_type: - continue # Skip if no type - - # Use the API type directly - tool_output["type"] = item_type - - # Add call_id as "id" - handle both dict and object - if isinstance(output_item, dict): - call_id = output_item.get("call_id") or output_item.get("id") - else: - call_id = getattr(output_item, "call_id", None) or getattr(output_item, "id", None) - - if call_id: - tool_output["id"] = call_id - - # Add output field only if content recording is enabled - if _trace_responses_content: - # Add output field - parse JSON string if needed - if isinstance(output_item, dict): - output_value = output_item.get("output") - else: - output_value = getattr(output_item, "output", None) - - if output_value is not None: - # Try to parse JSON string into object - if isinstance(output_value, str): - try: - parsed_output = json.loads(output_value) - # For computer_call_output, strip binary data if binary tracing is disabled - if item_type == "computer_call_output" and not _trace_binary_data: - if isinstance(parsed_output, dict): - output_type = parsed_output.get("type") - # Remove image_url from computer_screenshot if binary data tracing is off - if output_type == "computer_screenshot" and "image_url" in parsed_output: - parsed_output = { - k: v for k, v in parsed_output.items() if k != "image_url" - } - tool_output["output"] = parsed_output - except (json.JSONDecodeError, TypeError): - # If parsing fails, keep as string - tool_output["output"] = output_value - else: - # For non-string output, also check for binary data in computer outputs - if item_type == "computer_call_output" and not _trace_binary_data: - if isinstance(output_value, dict): - output_type = output_value.get("type") - if output_type == "computer_screenshot" and "image_url" in output_value: - output_value = {k: v for k, v in output_value.items() if k != "image_url"} - tool_output["output"] = output_value - - # Add to parts array - # Check if simple tool format is enabled for function_call_output types - if _get_use_simple_tool_format() and item_type == "function_call_output": - # Build simplified OTEL-compliant format: - # {"type": "tool_call_response", "id": "...", "result": "..."} - simple_response: Dict[str, Any] = {"type": "tool_call_response"} - if "id" in tool_output: - simple_response["id"] = tool_output["id"] - if _trace_responses_content and "output" in tool_output: - simple_response["result"] = tool_output["output"] - parts.append(simple_response) - else: - # Original nested format (type "tool_call_output" wraps the tool output) - # Always include type and id, even when content recording is disabled - parts.append({"type": "tool_call_output", "content": tool_output}) - except Exception: # pylint: disable=broad-exception-caught - # Skip items that can't be processed - logger.debug( - "Failed to process tool output item: %s", - output_item, - exc_info=True, - ) - continue - - # Always include parts array with type and id, even when content recording is disabled - content_array = [{"role": "tool", "parts": parts}] if parts else [] - - if _get_use_message_events(): - # Event-based mode: add events - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="tool", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Use "tool" for the event name: gen_ai.tool.message - span.span_instance.add_event(name=GEN_AI_TOOL_MESSAGE_EVENT, attributes=attributes) - else: - # Attribute-based mode: append to input messages (tool outputs are inputs to the model) - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - - def _add_mcp_response_events( - self, - span: "AbstractSpan", - mcp_responses: List[Any], - conversation_id: Optional[str] = None, - ) -> None: - """Add MCP response events (user-provided responses like approval) to the span.""" - parts: List[Dict[str, Any]] = [] - - # Always iterate through mcp_responses to build metadata - if mcp_responses: - for response_item in mcp_responses: - try: - mcp_response: Dict[str, Any] = {} - - # Get the item type - handle both dict and object attributes - if isinstance(response_item, dict): - item_type = response_item.get("type") - else: - item_type = getattr(response_item, "type", None) - - if not item_type: - continue # Skip if no type - - # Use the full MCP type (e.g., "mcp_approval_response") - mcp_response["type"] = item_type - - # Add id/approval_request_id - handle both dict and object - if isinstance(response_item, dict): - response_id = response_item.get("id") or response_item.get("approval_request_id") - else: - response_id = getattr(response_item, "id", None) or getattr( - response_item, "approval_request_id", None - ) - - if response_id: - mcp_response["id"] = response_id - - # Add additional fields only if content recording is enabled - if _trace_responses_content: - # Add approval-specific fields - if isinstance(response_item, dict): - for field in ["approve", "approval_request_id", "status"]: - if field in response_item and response_item[field] is not None: - mcp_response[field] = response_item[field] - else: - for field in ["approve", "approval_request_id", "status"]: - if hasattr(response_item, field): - value = getattr(response_item, field) - if value is not None: - mcp_response[field] = value - - # Add to parts array (type "mcp" wraps the MCP response) - # Always include type and id, even when content recording is disabled - parts.append({"type": "mcp", "content": mcp_response}) - except Exception: # pylint: disable=broad-exception-caught - # Skip items that can't be processed - logger.debug( - "Failed to process MCP response item: %s", - response_item, - exc_info=True, - ) - continue - - # Always include parts array with type and id, even when content recording is disabled - content_array = [{"role": "user", "parts": parts}] if parts else [] - - if _get_use_message_events(): - # Event-based mode: add events - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="user", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Use user message event name since MCP responses are user inputs - span.span_instance.add_event(name=GEN_AI_USER_MESSAGE_EVENT, attributes=attributes) - else: - # Attribute-based mode: append to input messages (MCP responses are user inputs) - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - - def _add_workflow_action_events( - self, - span: "AbstractSpan", - response: Any, - conversation_id: Optional[str] = None, - ) -> None: - """Add workflow action events to the span for workflow agents.""" - if not span or not span.span_instance.is_recording: - return - - # Check if response has output items - if not hasattr(response, "output") or not response.output: - return - - # Iterate through output items looking for workflow_action types - for output_item in response.output: - item_type = getattr(output_item, "type", None) - - if item_type == "workflow_action": - # Extract workflow action attributes - action_id = getattr(output_item, "action_id", None) - status = getattr(output_item, "status", None) - previous_action_id = getattr(output_item, "previous_action_id", None) - workflow_action_id = getattr(output_item, "id", None) - - # Create event attributes - event_attributes = { - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - } - - # Build workflow action details object - workflow_details: Dict[str, Any] = {} - - if _trace_responses_content: - # Include action details in content using optimized format - if status: - workflow_details["status"] = status - if action_id: - workflow_details["action_id"] = action_id - if previous_action_id: - workflow_details["previous_action_id"] = previous_action_id - else: - # When content recording is off, only include status - if status: - workflow_details["status"] = status - - # Use consistent format with role and parts wrapper - content_array = [ - { - "role": "workflow", - "parts": [{"type": "workflow_action", "content": workflow_details}], - } - ] - - # Store as JSON array directly without outer wrapper - event_attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Add the workflow action event - span.span_instance.add_event(name=GEN_AI_WORKFLOW_ACTION_EVENT, attributes=event_attributes) - - # pylint: disable=too-many-branches - def _add_structured_input_events( - self, - span: "AbstractSpan", - input_list: List[Any], - conversation_id: Optional[str] = None, - ) -> None: - """ - Add message events for structured input (list format). - This handles cases like messages with images, multi-part content, etc. - """ - for input_item in input_list: - try: - # Extract role - handle both dict and object - if isinstance(input_item, dict): - role = input_item.get("role", "user") - content = input_item.get("content") - else: - role = getattr(input_item, "role", "user") - content = getattr(input_item, "content", None) - - if not content: - continue - - # Build parts array with content parts - parts: List[Dict[str, Any]] = [] - has_content = False - - # Content can be a list of content items - if isinstance(content, list): - for content_item in content: - content_type = None - has_content = True - - # Handle dict format - if isinstance(content_item, dict): - content_type = content_item.get("type") - if content_type in ("input_text", "text"): - if _trace_responses_content: - text = content_item.get("text") - if text: - parts.append({"type": "text", "content": text}) - else: - parts.append({"type": "text"}) - elif content_type == "input_image": - image_part: Dict[str, Any] = {"type": "image"} - # Include image data if binary data tracing is enabled - if _trace_responses_content and _trace_binary_data: - image_url = content_item.get("image_url") - if image_url: - image_part["content"] = image_url - parts.append(image_part) - elif content_type == "input_file": - file_part: Dict[str, Any] = {"type": "file"} - if _trace_responses_content: - file_content: Dict[str, Any] = {} - # Only include filename and file_id if content recording is enabled - filename = content_item.get("filename") - if filename: - file_content["filename"] = filename - file_id = content_item.get("file_id") - if file_id: - file_content["file_id"] = file_id - # Only include file_data if binary data tracing is enabled - if _trace_binary_data: - file_data = content_item.get("file_data") - if file_data: - file_content["file_data"] = file_data - if file_content: - file_part["content"] = file_content - parts.append(file_part) - elif content_type: - # Other content types (audio, video, etc.) - parts.append({"type": content_type}) - - # Handle object format - elif hasattr(content_item, "type"): - content_type = getattr(content_item, "type", None) - if content_type in ("input_text", "text"): - if _trace_responses_content: - text = getattr(content_item, "text", None) - if text: - parts.append({"type": "text", "content": text}) - else: - parts.append({"type": "text"}) - elif content_type == "input_image": - image_part = {"type": "image"} - # Include image data if binary data tracing is enabled - if _trace_responses_content and _trace_binary_data: - image_url = getattr(content_item, "image_url", None) - if image_url: - image_part["content"] = image_url - parts.append(image_part) - elif content_type == "input_file": - file_part = {"type": "file"} - if _trace_responses_content: - file_content = {} - # Only include filename and file_id if content recording is enabled - filename = getattr(content_item, "filename", None) - if filename: - file_content["filename"] = filename - file_id = getattr(content_item, "file_id", None) - if file_id: - file_content["file_id"] = file_id - # Only include file_data if binary data tracing is enabled - if _trace_binary_data: - file_data = getattr(content_item, "file_data", None) - if file_data: - file_content["file_data"] = file_data - if file_content: - file_part["content"] = file_content - parts.append(file_part) - elif content_type: - # Other content types - parts.append({"type": content_type}) - - # Always create role object and include parts if we detected content - role_obj: Dict[str, Any] = {"role": role} - if parts: - role_obj["parts"] = parts - content_array = [role_obj] - - if _get_use_message_events(): - # Event-based mode - # Create event attributes - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role=role, - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json.dumps(content_array, ensure_ascii=False) - - # Map role to appropriate event name constant - if role == "user": - event_name = GEN_AI_USER_MESSAGE_EVENT - elif role == "assistant": - event_name = GEN_AI_ASSISTANT_MESSAGE_EVENT - else: - # Fallback for any other roles (shouldn't happen in practice) - event_name = f"gen_ai.{role}.message" - - # Add the event - span.span_instance.add_event(name=event_name, attributes=attributes) - else: - # Attribute-based mode - # Append messages to the appropriate attribute - if role in ("user", "tool"): - # User and tool messages go to input.messages - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - elif role == "assistant": - # Assistant messages go to output.messages - self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) - - except Exception: # pylint: disable=broad-exception-caught - # Skip items that can't be processed - logger.debug( - "Failed to process structured input item: %s", - input_item, - exc_info=True, - ) - continue - - def _emit_tool_call_event( - self, - span: "AbstractSpan", - tool_call: Dict[str, Any], - conversation_id: Optional[str] = None, - ) -> None: - """Helper to emit a single tool call event or attribute.""" - # Check if simple tool format is enabled for function_call types - if _get_use_simple_tool_format() and tool_call.get("type") == "function_call": - # Build simplified OTEL-compliant format: - # {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} - simple_tool_call: Dict[str, Any] = {"type": "tool_call"} - if "id" in tool_call: - simple_tool_call["id"] = tool_call["id"] - # Extract name and arguments from nested function object if content recording enabled - if _trace_responses_content and "function" in tool_call: - func = tool_call["function"] - if "name" in func: - simple_tool_call["name"] = func["name"] - if "arguments" in func: - simple_tool_call["arguments"] = func["arguments"] - parts = [simple_tool_call] - else: - # Original nested format - parts = [{"type": "tool_call", "content": tool_call}] - - content_array = [{"role": "assistant", "parts": parts}] - - if _get_use_message_events(): - # Original event-based implementation - json_content = json.dumps(content_array, ensure_ascii=False) - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="assistant", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json_content - span.span_instance.add_event(name=GEN_AI_ASSISTANT_MESSAGE_EVENT, attributes=attributes) - else: - # New attribute-based implementation - tool calls are output messages - self._append_to_message_attribute(span, GEN_AI_OUTPUT_MESSAGES, content_array) - - def _emit_tool_output_event( - self, - span: "AbstractSpan", - tool_output: Dict[str, Any], - conversation_id: Optional[str] = None, - ) -> None: - """Helper to emit a single tool output event or attribute.""" - # Check if simple tool format is enabled for function_call_output types - if _get_use_simple_tool_format() and tool_output.get("type") == "function_call_output": - # Build simplified OTEL-compliant format: - # {"type": "tool_call_response", "id": "...", "result": "..."} - simple_tool_response: Dict[str, Any] = {"type": "tool_call_response"} - if "id" in tool_output: - simple_tool_response["id"] = tool_output["id"] - # Add result only if content recording is enabled - if _trace_responses_content and "output" in tool_output: - simple_tool_response["result"] = tool_output["output"] - parts = [simple_tool_response] - else: - # Original nested format - parts = [{"type": "tool_call_output", "content": tool_output}] - - content_array = [{"role": "tool", "parts": parts}] - - if _get_use_message_events(): - # Original event-based implementation - json_content = json.dumps(content_array, ensure_ascii=False) - attributes = self._create_event_attributes( - conversation_id=conversation_id, - message_role="tool", - ) - # Store as JSON array directly without outer wrapper - attributes[GEN_AI_EVENT_CONTENT] = json_content - # Tool outputs are inputs to the model, so use input.messages event - span.span_instance.add_event(name=GEN_AI_USER_MESSAGE_EVENT, attributes=attributes) - else: - # New attribute-based implementation - tool outputs are input messages - self._append_to_message_attribute(span, GEN_AI_INPUT_MESSAGES, content_array) - - def _add_tool_call_events( # pylint: disable=too-many-branches - self, - span: "AbstractSpan", - response: Any, - conversation_id: Optional[str] = None, - ) -> None: - """Add tool call events to the span from response output.""" - if not span or not span.span_instance.is_recording: - return - - # Extract function calls and tool calls from response output - output = getattr(response, "output", None) - if not output: - return - - # Process output items for tool call events - for output_item in output: - try: - item_type = getattr(output_item, "type", None) - # Process item based on type - if not item_type: - continue - - tool_call: Dict[str, Any] # Declare once for all branches - - # Handle function_call type - if item_type == "function_call": - tool_call = { - "type": item_type, - } - - # Always include id (needed to correlate with function output) - if hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include function name and arguments if content recording is enabled - if _trace_responses_content: - function_details: Dict[str, Any] = {} - if hasattr(output_item, "name"): - function_details["name"] = output_item.name - if hasattr(output_item, "arguments"): - function_details["arguments"] = output_item.arguments - if function_details: - tool_call["function"] = function_details - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle file_search_call type - elif item_type == "file_search_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include search details if content recording is enabled - if _trace_responses_content: - # queries and results are directly on the item - if hasattr(output_item, "queries") and output_item.queries: - tool_call["queries"] = output_item.queries - if hasattr(output_item, "results") and output_item.results: - tool_call["results"] = [] - for result in output_item.results: - result_data = { - "file_id": getattr(result, "file_id", None), - "filename": getattr(result, "filename", None), - "score": getattr(result, "score", None), - } - tool_call["results"].append(result_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle code_interpreter_call type - elif item_type == "code_interpreter_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include code interpreter details if content recording is enabled - if _trace_responses_content: - # code and outputs are directly on the item - if hasattr(output_item, "code") and output_item.code: - tool_call["code"] = output_item.code - if hasattr(output_item, "outputs") and output_item.outputs: - tool_call["outputs"] = [] - for output in output_item.outputs: - # Outputs can be logs or images - output_data = { - "type": getattr(output, "type", None), - } - if hasattr(output, "logs"): - output_data["logs"] = output.logs - elif hasattr(output, "image"): - output_data["image"] = {"file_id": getattr(output.image, "file_id", None)} - tool_call["outputs"].append(output_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle web_search_call type - elif item_type == "web_search_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include search action if content recording is enabled - if _trace_responses_content: - # action is directly on the item - if hasattr(output_item, "action") and output_item.action: - # WebSearchAction has type and type-specific fields - tool_call["action"] = { - "type": getattr(output_item.action, "type", None), - } - # Try to capture action-specific fields - if hasattr(output_item.action, "query"): - tool_call["action"]["query"] = output_item.action.query - if hasattr(output_item.action, "results"): - tool_call["action"]["results"] = [] - for result in output_item.action.results: - result_data = { - "title": getattr(result, "title", None), - "url": getattr(result, "url", None), - } - tool_call["action"]["results"].append(result_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle azure_ai_search_call type - elif item_type == "azure_ai_search_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include search details if content recording is enabled - if _trace_responses_content: - # Add Azure AI Search specific fields - if hasattr(output_item, "input") and output_item.input: - tool_call["input"] = output_item.input - - if hasattr(output_item, "results") and output_item.results: - tool_call["results"] = [] - for result in output_item.results: - result_data = {} - if hasattr(result, "title"): - result_data["title"] = result.title - if hasattr(result, "url"): - result_data["url"] = result.url - if hasattr(result, "content"): - result_data["content"] = result.content - if result_data: - tool_call["results"].append(result_data) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle image_generation_call type - elif item_type == "image_generation_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include image generation details if content recording is enabled - if _trace_responses_content: - # Include metadata fields - if hasattr(output_item, "prompt"): - tool_call["prompt"] = output_item.prompt - if hasattr(output_item, "quality"): - tool_call["quality"] = output_item.quality - if hasattr(output_item, "size"): - tool_call["size"] = output_item.size - if hasattr(output_item, "style"): - tool_call["style"] = output_item.style - - # Include the result (image data) only if binary data tracing is enabled - if _trace_binary_data and hasattr(output_item, "result") and output_item.result: - tool_call["result"] = output_item.result - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle mcp_call type (Model Context Protocol) - elif item_type == "mcp_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - - # Only include MCP details if content recording is enabled - if _trace_responses_content: - if hasattr(output_item, "name"): - tool_call["name"] = output_item.name - if hasattr(output_item, "arguments"): - tool_call["arguments"] = output_item.arguments - if hasattr(output_item, "server_label"): - tool_call["server_label"] = output_item.server_label - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle other MCP types (mcp_list_tools, mcp_approval_request, etc.) - elif item_type and item_type.startswith("mcp_"): - tool_call = { - "type": item_type, # Preserve the specific MCP type - } - - # Always include ID if available - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - - # Only include additional details if content recording is enabled - if _trace_responses_content: - # Try to capture common MCP fields - for field in [ - "name", - "server_label", - "arguments", - "approval_request_id", - "approve", - "status", - ]: - if hasattr(output_item, field): - value = getattr(output_item, field) - if value is not None: - tool_call[field] = value - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle computer_call type (for computer use) - elif item_type == "computer_call": - tool_call = { - "type": item_type, - } - - if hasattr(output_item, "call_id"): - tool_call["call_id"] = output_item.call_id - - # Only include computer action details if content recording is enabled - if _trace_responses_content: - # action is directly on the item - if hasattr(output_item, "action") and output_item.action: - # ComputerAction has type and type-specific fields - tool_call["action"] = { - "type": getattr(output_item.action, "type", None), - } - # Try to capture common action fields - for attr in ["x", "y", "text", "key", "command", "scroll"]: - if hasattr(output_item.action, attr): - tool_call["action"][attr] = getattr(output_item.action, attr) - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle remote_function_call_output type (remote tool calls like Azure AI Search) - elif item_type == "remote_function_call_output": - # Extract the tool name from the output item - tool_name = getattr(output_item, "name", None) if hasattr(output_item, "name") else None - - tool_call = { - "type": tool_name if tool_name else "remote_function", - } - - # Always include ID (needed for correlation) - if hasattr(output_item, "id"): - tool_call["id"] = output_item.id - elif hasattr(output_item, "call_id"): - tool_call["id"] = output_item.call_id - # Check model_extra for call_id - elif hasattr(output_item, "model_extra") and isinstance(output_item.model_extra, dict): - if "call_id" in output_item.model_extra: - tool_call["id"] = output_item.model_extra["call_id"] - - # Only include tool details if content recording is enabled - if _trace_responses_content: - # Extract data from model_extra if available (Pydantic v2 style) - if hasattr(output_item, "model_extra") and isinstance(output_item.model_extra, dict): - for key, value in output_item.model_extra.items(): - # Skip already captured fields, redundant fields (name, label), and empty/None values - if ( - key not in ["type", "id", "call_id", "name", "label"] - and value is not None - and value != "" - ): - tool_call[key] = value - - # Also try as_dict if available - if hasattr(output_item, "as_dict"): - try: - tool_dict = output_item.as_dict() - # Extract relevant fields (exclude already captured ones and empty/None values) - for key, value in tool_dict.items(): - if key not in [ - "type", - "id", - "call_id", - "name", - "label", - "role", - "content", - ]: - # Skip empty strings and None values - if value is not None and value != "": - # Don't overwrite if already exists - if key not in tool_call: - tool_call[key] = value - except Exception as e: - logger.debug(f"Failed to extract data from as_dict: {e}") - - # Fallback: try common fields directly (skip if empty and skip redundant name/label) - for field in [ - "input", - "output", - "results", - "status", - "error", - "search_query", - "query", - ]: - if hasattr(output_item, field): - try: - value = getattr(output_item, field) - if value is not None and value != "": - # If not already in tool_call, add it - if field not in tool_call: - tool_call[field] = value - except Exception: - pass - - self._emit_tool_call_event(span, tool_call, conversation_id) - - # Handle unknown/future tool call types with best effort - # Exclude _output types - those are handled separately as tool outputs, not tool calls - elif item_type and "_call" in item_type and not item_type.endswith("_output"): - # Generic handler for tool calls - try: - tool_call = { - "type": item_type, - } - - # Always try to include common ID fields (safe, needed for correlation) - for id_field in ["id", "call_id"]: - if hasattr(output_item, id_field): - tool_call["id" if id_field == "id" else "id"] = getattr(output_item, id_field) - break # Use first available ID field - - # Only include detailed fields if content recording is enabled - if _trace_responses_content: - # Try to get the full tool details using model_dump() for Pydantic models - if hasattr(output_item, "model_dump"): - tool_dict = output_item.model_dump() - # Extract the tool-specific details (exclude common fields already captured) - for key, value in tool_dict.items(): - if ( - key - not in ["type", "id", "call_id", "role", "content", "status", "partition_key"] - and value is not None - ): - tool_call[key] = value - elif hasattr(output_item, "as_dict"): - tool_dict = output_item.as_dict() - # Extract the tool-specific details (exclude common fields already captured) - for key, value in tool_dict.items(): - if key not in ["type", "id", "call_id"] and value is not None: - tool_call[key] = value - else: - # Fallback: try to capture common fields manually - for field in [ - "name", - "arguments", - "input", - "query", - "search_query", - "server_label", - ]: - if hasattr(output_item, field): - value = getattr(output_item, field) - if value is not None: - tool_call[field] = value - - self._emit_tool_call_event(span, tool_call, conversation_id) - - except Exception as e: - # Log but don't crash if we can't handle an unknown tool type - logger.debug(f"Failed to process unknown tool call type '{item_type}': {e}") - - # Handle unknown/future tool output types with best effort - # These are the _output types that correspond to the tool calls above - elif item_type and item_type.endswith("_output"): - # Generic handler for tool outputs - try: - tool_output = { - "type": item_type, - } - - # Always try to include common ID fields (safe, needed for correlation) - for id_field in ["id", "call_id"]: - if hasattr(output_item, id_field): - tool_output["id"] = getattr(output_item, id_field) - break # Use first available ID field - - # Only include detailed fields if content recording is enabled - if _trace_responses_content: - # Try to get the full tool output using model_dump() for Pydantic models - if hasattr(output_item, "model_dump"): - output_dict = output_item.model_dump() - # Extract the tool-specific output (exclude common fields already captured) - # Include fields even if empty string (but not None) for API consistency - for key, value in output_dict.items(): - if ( - key - not in ["type", "id", "call_id", "role", "content", "status", "partition_key"] - and value is not None - ): - tool_output[key] = value - elif hasattr(output_item, "as_dict"): - output_dict = output_item.as_dict() - # Extract the tool-specific output (exclude common fields already captured) - for key, value in output_dict.items(): - if ( - key not in ["type", "id", "call_id", "role", "content", "status"] - and value is not None - ): - tool_output[key] = value - else: - # Fallback: try to capture common output fields manually - for field in [ - "output", - "result", - "results", - "data", - "response", - ]: - if hasattr(output_item, field): - value = getattr(output_item, field) - if value is not None: - tool_output[field] = value - - self._emit_tool_output_event(span, tool_output, conversation_id) - - except Exception as e: - # Log but don't crash if we can't handle an unknown tool output type - logger.debug(f"Failed to process unknown tool output type '{item_type}': {e}") - - except Exception as e: - # Catch-all to prevent any tool call processing errors from breaking instrumentation - logger.debug(f"Error processing tool call events: {e}") - - def start_responses_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - model: Optional[str] = None, - assistant_name: Optional[str] = None, - agent_id: Optional[str] = None, - conversation_id: Optional[str] = None, - input_text: Optional[str] = None, - input_raw: Optional[Any] = None, - stream: bool = False, # pylint: disable=unused-argument - tools: Optional[List[Dict[str, Any]]] = None, - ) -> "Optional[AbstractSpan]": - """Start a span for responses API call.""" - # Build span name: agent case uses "invoke_agent", non-agent case uses "chat" - if assistant_name: - span_name = f"{SPAN_NAME_INVOKE_AGENT} {assistant_name}" - operation_name_value = OPERATION_NAME_INVOKE_AGENT - elif model: - span_name = f"{SPAN_NAME_CHAT} {model}" - operation_name_value = OPERATION_NAME_CHAT - else: - span_name = OperationName.RESPONSES.value - operation_name_value = OperationName.RESPONSES.value - - span = start_span( - operation_name=OperationName.RESPONSES, - server_address=server_address, - port=port, - span_name=span_name, - model=model, - gen_ai_provider=RESPONSES_PROVIDER, - ) - - if span and span.span_instance.is_recording: - # Set operation name attribute (start_span doesn't set this automatically) - self._set_attributes( - span, - (GEN_AI_OPERATION_NAME, operation_name_value), - ) - - # Set response-specific attributes that start_span doesn't handle - # Note: model and server_address are already set by start_span, so we don't need to set them again - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - self._set_span_attribute_safe(span, GEN_AI_AGENT_NAME, assistant_name) - self._set_span_attribute_safe(span, GEN_AI_AGENT_ID, agent_id) - - # Set tools attribute if tools are provided - if tools: - # Convert tools list to JSON string for the attribute - tools_json = json.dumps(tools, ensure_ascii=False) - self._set_span_attribute_safe(span, GEN_AI_REQUEST_TOOLS, tools_json) - - # Process input - check if it contains tool outputs or MCP responses - tool_outputs = [] - mcp_responses = [] - has_tool_outputs = False - has_mcp_responses = False - - # Use input_raw (or input_text if it's a list) to check for tool outputs - input_to_check = input_raw if input_raw is not None else input_text - - # Check if input is a list (structured input with potential tool outputs) - if isinstance(input_to_check, list): - for item in input_to_check: - # Check if this item has type "function_call_output" or similar - item_type = None - if hasattr(item, "type"): - item_type = getattr(item, "type", None) - elif isinstance(item, dict): - item_type = item.get("type") - - if item_type and ("output" in item_type or item_type == "function_call_output"): - has_tool_outputs = True - tool_outputs.append(item) - elif item_type and item_type.startswith("mcp_") and "response" in item_type: - # MCP responses (mcp_approval_response, etc.) are user inputs - has_mcp_responses = True - mcp_responses.append(item) - - # Add appropriate message events based on input type - if has_tool_outputs: - # Add tool message event for tool outputs - self._add_tool_message_events( - span, - tool_outputs=tool_outputs, - conversation_id=conversation_id, - ) - elif has_mcp_responses: - # Add MCP response events (user providing approval/response) - self._add_mcp_response_events( - span, - mcp_responses=mcp_responses, - conversation_id=conversation_id, - ) - elif input_text and not isinstance(input_text, list): - # Add regular user message event (only if input_text is a string, not a list) - self._add_message_event( - span, - role="user", - content=input_text, - conversation_id=conversation_id, - ) - elif isinstance(input_to_check, list) and not has_tool_outputs: - # Handle structured input (list format) - extract text content from user messages - # This handles cases like image inputs with text prompts - self._add_structured_input_events( - span, - input_list=input_to_check, - conversation_id=conversation_id, - ) - - return span - - def _extract_server_info_from_client( - self, client: Any - ) -> Tuple[Optional[str], Optional[int]]: # pylint: disable=docstring-missing-return,docstring-missing-rtype - """Extract server address and port from OpenAI client.""" - try: - # First try direct access to base_url - if hasattr(client, "base_url") and client.base_url: - return self._parse_url(str(client.base_url)) - if hasattr(client, "_base_url") and client._base_url: # pylint: disable=protected-access - return self._parse_url(str(client._base_url)) - - # Try the nested client structure as suggested - base_client = getattr(client, "_client", None) - if base_client: - base_url = getattr(base_client, "base_url", None) - if base_url: - return self._parse_url(str(base_url)) - except Exception: # pylint: disable=broad-exception-caught - pass - return None, None - - def _extract_conversation_id(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract conversation ID from kwargs.""" - return kwargs.get("conversation") or kwargs.get("conversation_id") - - def _extract_model(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract model from kwargs.""" - return kwargs.get("model") - - def _extract_assistant_name(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract assistant/agent name from kwargs.""" - extra_body = kwargs.get("extra_body") - if extra_body and isinstance(extra_body, dict): - agent_info = extra_body.get("agent_reference") - if agent_info and isinstance(agent_info, dict): - return agent_info.get("name") - return None - - def _extract_agent_id(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract agent ID from kwargs.""" - extra_body = kwargs.get("extra_body") - if extra_body and isinstance(extra_body, dict): - agent_info = extra_body.get("agent_reference") - if agent_info and isinstance(agent_info, dict): - return agent_info.get("id") - return None - - def _extract_input_text(self, kwargs: Dict[str, Any]) -> Optional[str]: - """Extract input text from kwargs.""" - return kwargs.get("input") - - def _extract_finish_reason(self, response: Any) -> Optional[str]: - """Extract finish reason from response output.""" - if hasattr(response, "output") and response.output: - try: - # Check if output is a list (typical case) - if isinstance(response.output, list) and len(response.output) > 0: - output_item = response.output[0] # Get first output item - - # Try finish_reason field - if hasattr(output_item, "finish_reason") and output_item.finish_reason: - return output_item.finish_reason - - # Try finish_details.type (Azure AI Agents structure) - if hasattr(output_item, "finish_details") and output_item.finish_details: - if hasattr(output_item.finish_details, "type"): - return output_item.finish_details.type - except (AttributeError, TypeError, IndexError): - pass - - # Fallback: check response.status directly - if hasattr(response, "status"): - return response.status - - return None - - def _extract_output_text(self, response: Any) -> Optional[str]: - """Extract output text from response.""" - if hasattr(response, "output") and response.output: - # Handle simple string output (for tests/simple cases) - if isinstance(response.output, str): - return response.output - - # Handle complex output structure (list of response messages) - output_texts = [] - try: - for output_item in response.output: - if hasattr(output_item, "content") and output_item.content: - # content is typically a list of content blocks - for content_block in output_item.content: - if hasattr(content_block, "text"): - output_texts.append(content_block.text) - elif hasattr(content_block, "output_text") and hasattr(content_block.output_text, "text"): - # Handle ResponseOutputText structure - output_texts.append(content_block.output_text.text) - elif isinstance(content_block, str): - output_texts.append(content_block) - elif isinstance(output_item, str): - # Handle simple string items - output_texts.append(output_item) - - if output_texts: - return " ".join(output_texts) - except (AttributeError, TypeError): - # Fallback: convert to string but log for debugging - logger.debug( - "Failed to extract structured output text, falling back to string conversion: %s", - response.output, - ) - return str(response.output) - return None - - def _extract_responses_api_attributes(self, span: "AbstractSpan", response: Any) -> None: - """Extract and set attributes for Responses API responses.""" - try: - # Extract and set response model - model = getattr(response, "model", None) - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_MODEL, model) - - # Extract and set response ID - response_id = getattr(response, "id", None) - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_ID, response_id) - - # Extract and set system fingerprint if available - system_fingerprint = getattr(response, "system_fingerprint", None) - self._set_span_attribute_safe(span, GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT, system_fingerprint) - - # Extract and set usage information (Responses API may use input_tokens/output_tokens) - usage = getattr(response, "usage", None) - if usage: - # Try input_tokens first, then prompt_tokens for compatibility - input_tokens = getattr(usage, "input_tokens", None) or getattr(usage, "prompt_tokens", None) - # Try output_tokens first, then completion_tokens for compatibility - output_tokens = getattr(usage, "output_tokens", None) or getattr(usage, "completion_tokens", None) - # total_tokens = getattr(usage, "total_tokens", None) # Unused - - self._set_span_attribute_safe(span, GEN_AI_USAGE_INPUT_TOKENS, input_tokens) - self._set_span_attribute_safe(span, GEN_AI_USAGE_OUTPUT_TOKENS, output_tokens) - # self._set_span_attribute_safe(span, GEN_AI_USAGE_TOTAL_TOKENS, total_tokens) # Commented out as redundant - - # Extract finish reasons from output items (Responses API structure) - output = getattr(response, "output", None) - if output: - finish_reasons = [] - for item in output: - if hasattr(item, "finish_reason") and item.finish_reason: - finish_reasons.append(item.finish_reason) - - if finish_reasons: - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_FINISH_REASONS, finish_reasons) - else: - # Handle single finish reason (not in output array) - finish_reason = getattr(response, "finish_reason", None) - if finish_reason: - self._set_span_attribute_safe(span, GEN_AI_RESPONSE_FINISH_REASONS, [finish_reason]) - - except Exception as e: - logger.debug(f"Error extracting responses API attributes: {e}") - - def _extract_conversation_attributes(self, span: "AbstractSpan", response: Any) -> None: - """Extract and set attributes for conversation creation responses.""" - try: - # Extract and set conversation ID - conversation_id = getattr(response, "id", None) - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - - # Set response object type - # self._set_span_attribute_safe(span, GEN_AI_RESPONSE_OBJECT, "conversation") - - except Exception as e: - logger.debug(f"Error extracting conversation attributes: {e}") - - def _extract_conversation_items_attributes( - self, span: "AbstractSpan", response: Any, args: Tuple, kwargs: Dict[str, Any] - ) -> None: - """Extract and set attributes for conversation items list responses.""" - try: - # Set response object type for list operations - # self._set_span_attribute_safe(span, GEN_AI_RESPONSE_OBJECT, "list") - - # Extract conversation_id from request parameters - conversation_id = None - if args and len(args) > 1: - # Second argument might be conversation_id - conversation_id = args[1] - elif "conversation_id" in kwargs: - conversation_id = kwargs["conversation_id"] - - if conversation_id: - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - - # Note: Removed gen_ai.response.has_more attribute as requested - - except Exception as e: - logger.debug(f"Error extracting conversation items attributes: {e}") - - def _extract_response_attributes(self, response: Any) -> Dict[str, Any]: - """Extract response attributes from response object (legacy method for backward compatibility).""" - attributes = {} - - try: - # Extract response model - model = getattr(response, "model", None) - if model: - attributes[GEN_AI_RESPONSE_MODEL] = model - - # Extract response ID - response_id = getattr(response, "id", None) - if response_id: - attributes[GEN_AI_RESPONSE_ID] = response_id - - # Extract usage information - usage = getattr(response, "usage", None) - if usage: - prompt_tokens = getattr(usage, "prompt_tokens", None) - completion_tokens = getattr(usage, "completion_tokens", None) - # total_tokens = getattr(usage, "total_tokens", None) # Unused - - if prompt_tokens: - attributes[GEN_AI_USAGE_INPUT_TOKENS] = prompt_tokens - if completion_tokens: - attributes[GEN_AI_USAGE_OUTPUT_TOKENS] = completion_tokens - # if total_tokens: - # attributes[GEN_AI_USAGE_TOTAL_TOKENS] = total_tokens # Commented out as redundant - - # Extract finish reasons from output items (Responses API structure) - output = getattr(response, "output", None) - if output: - finish_reasons = [] - for item in output: - if hasattr(item, "finish_reason") and item.finish_reason: - finish_reasons.append(item.finish_reason) - - if finish_reasons: - attributes[GEN_AI_RESPONSE_FINISH_REASONS] = finish_reasons - else: - finish_reason = getattr(response, "finish_reason", None) - if finish_reason: - attributes[GEN_AI_RESPONSE_FINISH_REASONS] = [finish_reason] - - except Exception as e: - logger.debug(f"Error extracting response attributes: {e}") - - return attributes - - def _create_responses_span_from_parameters(self, *args, **kwargs): - """Extract parameters and create span for responses API tracing.""" - # Extract client from args (first argument) - client = args[0] if args else None - server_address, port = self._extract_server_info_from_client(client) - - # Extract parameters from kwargs - conversation_id = self._extract_conversation_id(kwargs) - model = self._extract_model(kwargs) - assistant_name = self._extract_assistant_name(kwargs) - agent_id = self._extract_agent_id(kwargs) - input_text = self._extract_input_text(kwargs) - input_raw = kwargs.get("input") # Get the raw input (could be string or list) - stream = kwargs.get("stream", False) - - # Create and return the span - return self.start_responses_span( - server_address=server_address, - port=port, - model=model, - assistant_name=assistant_name, - agent_id=agent_id, - conversation_id=conversation_id, - input_text=input_text, - input_raw=input_raw, - stream=stream, - ) - - def trace_responses_create(self, function, *args, **kwargs): - """Trace synchronous responses.create calls.""" - # If stream=True and we're being called from responses.stream(), skip tracing - # The responses.stream() method internally calls create(stream=True), and - # trace_responses_stream() will handle the tracing for that case. - # We only trace direct calls to create(stream=True) from user code. - if kwargs.get("stream", False): - # Check if we're already in a stream tracing context - # by looking at the call stack - import inspect - - frame = inspect.currentframe() - if frame and frame.f_back and frame.f_back.f_back: - # Check if the caller is trace_responses_stream - caller_name = frame.f_back.f_back.f_code.co_name - if caller_name in ( - "trace_responses_stream", - "trace_responses_stream_async", - "__enter__", - "__aenter__", - ): - # We're being called from responses.stream(), don't create a new span - return function(*args, **kwargs) - - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Handle streaming vs non-streaming responses differently - stream = kwargs.get("stream", False) - if stream: - # For streaming, don't use context manager - let wrapper handle span lifecycle - try: - result = function(*args, **kwargs) - result = self._wrap_streaming_response( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - else: - # For non-streaming, use context manager as before - with span: - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set response attributes - self._extract_responses_api_attributes(span, result) - - # Add tool call events (if any) - conversation_id = self._extract_conversation_id(kwargs) - self._add_tool_call_events(span, result, conversation_id) - - # Add workflow action events (if any) - self._add_workflow_action_events(span, result, conversation_id) - - # Add assistant message event - output_text = self._extract_output_text(result) - if output_text: - finish_reason = self._extract_finish_reason(result) - self._add_message_event( - span, - role="assistant", - content=output_text, - conversation_id=conversation_id, - finish_reason=finish_reason, - ) - - # Record metrics using new dedicated method - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.OK) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - return result - - async def trace_responses_create_async(self, function, *args, **kwargs): - """Trace asynchronous responses.create calls.""" - # If stream=True and we're being called from responses.stream(), skip tracing - # The responses.stream() method internally calls create(stream=True), and - # trace_responses_stream() will handle the tracing for that case. - # We only trace direct calls to create(stream=True) from user code. - if kwargs.get("stream", False): - # Check if we're already in a stream tracing context - # by looking at the call stack - import inspect - - frame = inspect.currentframe() - if frame and frame.f_back and frame.f_back.f_back: - # Check if the caller is trace_responses_stream - caller_name = frame.f_back.f_back.f_code.co_name - if caller_name in ( - "trace_responses_stream", - "trace_responses_stream_async", - "__enter__", - "__aenter__", - ): - # We're being called from responses.stream(), don't create a new span - return await function(*args, **kwargs) - - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Handle streaming vs non-streaming responses differently - stream = kwargs.get("stream", False) - if stream: - # For streaming, don't use context manager - let wrapper handle span lifecycle - try: - result = await function(*args, **kwargs) - result = self._wrap_async_streaming_response( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - else: - # For non-streaming, use context manager as before - with span: - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set response attributes - self._extract_responses_api_attributes(span, result) - - # Add tool call events (if any) - conversation_id = self._extract_conversation_id(kwargs) - self._add_tool_call_events(span, result, conversation_id) - - # Add workflow action events (if any) - self._add_workflow_action_events(span, result, conversation_id) - - # Add assistant message event - output_text = self._extract_output_text(result) - if output_text: - finish_reason = self._extract_finish_reason(result) - self._add_message_event( - span, - role="assistant", - content=output_text, - conversation_id=conversation_id, - finish_reason=finish_reason, - ) - - # Record metrics using new dedicated method - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.OK) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - return result - - def trace_responses_stream(self, function, *args, **kwargs): - """Trace synchronous responses.stream calls.""" - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # No tracing, just call the function - return function(*args, **kwargs) - - # For responses.stream(), always wrap the ResponseStreamManager - try: - result = function(*args, **kwargs) - # Detect if it's async or sync stream manager by checking for __aenter__ - if hasattr(result, "__aenter__"): - # Async stream manager - result = self._wrap_async_response_stream_manager( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - else: - # Sync stream manager - result = self._wrap_response_stream_manager( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - - def trace_responses_stream_async(self, function, *args, **kwargs): - """Trace asynchronous responses.stream calls.""" - span = self._create_responses_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - model = self._extract_model(kwargs) - operation_name = "responses" - - start_time = time.time() - - if span is None: - # No tracing, just call the function (don't await - it returns async context manager) - return function(*args, **kwargs) - - # For responses.stream(), always wrap the AsyncResponseStreamManager - # Note: stream() itself is not async, it returns an AsyncResponseStreamManager synchronously - try: - result = function(*args, **kwargs) - # Wrap the AsyncResponseStreamManager - result = self._wrap_async_response_stream_manager( - result, - span, - kwargs, - start_time, - operation_name, - server_address, - port, - model, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: model, - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - self.record_error(span, e) - span.span_instance.end() - raise - - def _wrap_streaming_response( - self, - stream, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap a streaming response to trace chunks.""" - conversation_id = self._extract_conversation_id(original_kwargs) - instrumentor = self # Capture the instrumentor instance - - class StreamWrapper: # pylint: disable=too-many-instance-attributes,protected-access - def __init__( - self, - stream_iter, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.stream_iter = stream_iter - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.accumulated_content = [] - self.span_ended = False - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - - # Enhanced properties for sophisticated chunk processing - self.accumulated_output = [] - self.response_id = None - self.response_model = None - self.service_tier = None - self.input_tokens = 0 - self.output_tokens = 0 - self.finish_reason = None # Track finish_reason from streaming chunks - - # Track all output items from streaming events (tool calls, workflow actions, etc.) - # Use (id, type) as key to avoid overwriting when call and output have same ID - self.output_items = {} # Dict[(item_id, item_type), output_item] - self.has_output_items = False - - # Expose response attribute for compatibility with ResponseStreamManager - self.response = getattr(stream_iter, "response", None) or getattr(stream_iter, "_response", None) - - def append_output_content(self, content): - """Append content to accumulated output list.""" - if content: - self.accumulated_output.append(str(content)) - - def set_response_metadata(self, chunk): - """Update response metadata from chunk if not already set.""" - chunk_type = getattr(chunk, "type", None) - - if not self.response_id: - self.response_id = getattr(chunk, "id", None) - if not self.response_model: - self.response_model = getattr(chunk, "model", None) - if not self.service_tier: - self.service_tier = getattr(chunk, "service_tier", None) - - # Extract finish_reason from response.output_item.done events - if chunk_type == "response.output_item.done" and hasattr(chunk, "item"): - item = chunk.item - if hasattr(item, "status") and item.status: - self.finish_reason = item.status - # Also check for direct finish_reason attribute - elif hasattr(chunk, "finish_reason") and chunk.finish_reason: - self.finish_reason = chunk.finish_reason - # Also check for finish_details in output items (Azure AI Agents structure) - elif hasattr(chunk, "output") and chunk.output: - if isinstance(chunk.output, list) and len(chunk.output) > 0: - output_item = chunk.output[0] - if hasattr(output_item, "finish_details") and output_item.finish_details: - if hasattr(output_item.finish_details, "type"): - self.finish_reason = output_item.finish_details.type - - def process_chunk(self, chunk): - """Process chunk to accumulate data and update metadata.""" - # Check for output item events in streaming - chunk_type = getattr(chunk, "type", None) - - # Collect all complete output items from ResponseOutputItemDoneEvent or ResponseOutputItemAddedEvent - # This includes function_call, file_search_tool_call, code_interpreter_tool_call, - # web_search, mcp_call, computer_tool_call, custom_tool_call, workflow_action, and any future types - if (chunk_type in ("response.output_item.done", "response.output_item.added")) and hasattr( - chunk, "item" - ): - item = chunk.item - item_type = getattr(item, "type", None) - - # Collect any output item (tool calls, workflow actions, etc.) - if item_type: - # Use call_id, action_id, or id as the key (workflow actions use action_id) - item_id = ( - getattr(item, "call_id", None) - or getattr(item, "action_id", None) - or getattr(item, "id", None) - ) - if item_id: - # Use (id, type) tuple as key to distinguish call from output - key = (item_id, item_type) - self.output_items[key] = item - self.has_output_items = True - # Items without ID or type are skipped - - # Capture response ID from ResponseCreatedEvent or ResponseCompletedEvent - if chunk_type == "response.created" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - self.response_model = getattr(chunk.response, "model", None) - elif chunk_type == "response.completed" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - if not self.response_model: - self.response_model = getattr(chunk.response, "model", None) - # Extract usage from the completed response - if hasattr(chunk.response, "usage"): - response_usage = chunk.response.usage - if hasattr(response_usage, "input_tokens") and response_usage.input_tokens: - self.input_tokens = response_usage.input_tokens - if hasattr(response_usage, "output_tokens") and response_usage.output_tokens: - self.output_tokens = response_usage.output_tokens - # Also handle standard token field names for compatibility - if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens: - self.input_tokens = response_usage.prompt_tokens - if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens: - self.output_tokens = response_usage.completion_tokens - - # Only append TEXT content from delta events (not function call arguments or other deltas) - # Text deltas can come as: - # 1. response.text.delta - has delta as string - # 2. response.output_item.delta - has delta.text attribute - # Function call arguments come via response.function_call_arguments.delta - has delta as JSON string - # We need to avoid appending function call arguments - if chunk_type and ".delta" in chunk_type and hasattr(chunk, "delta"): - # If it's function_call_arguments.delta, skip it - if "function_call_arguments" not in chunk_type: - # Check if delta is a string (text content) or has .text attribute - if isinstance(chunk.delta, str): - self.append_output_content(chunk.delta) - elif hasattr(chunk.delta, "text"): - self.append_output_content(chunk.delta.text) - - # Always update metadata - self.set_response_metadata(chunk) - - # Handle usage info - usage = getattr(chunk, "usage", None) - if usage: - if hasattr(usage, "input_tokens") and usage.input_tokens: - self.input_tokens += usage.input_tokens - if hasattr(usage, "output_tokens") and usage.output_tokens: - self.output_tokens += usage.output_tokens - # Also handle standard token field names - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self.input_tokens += usage.prompt_tokens - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self.output_tokens += usage.completion_tokens - - def cleanup(self): - """Perform final cleanup when streaming is complete.""" - if not self.span_ended: - duration = time.time() - self.start_time - - # Join all accumulated output content - complete_content = "".join(self.accumulated_output) - - if self.span.span_instance.is_recording: - # Add tool call events if we detected any output items (tool calls, etc.) - if self.has_output_items: - # Create mock response with output items for event generation - # The existing _add_tool_call_events method handles all tool types - mock_response = type( - "Response", - (), - {"output": list(self.output_items.values())}, - )() - self.instrumentor._add_tool_call_events( - self.span, - mock_response, - self.conversation_id, - ) - # Also add workflow action events - self.instrumentor._add_workflow_action_events( - self.span, - mock_response, - self.conversation_id, - ) - - # Only add assistant message event if there's actual text content (not empty/whitespace) - if complete_content and complete_content.strip(): - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=complete_content, - conversation_id=self.conversation_id, - finish_reason=self.finish_reason, - ) - - # Set final span attributes using accumulated metadata - if self.response_id: - self.instrumentor._set_span_attribute_safe(self.span, GEN_AI_RESPONSE_ID, self.response_id) - if self.response_model: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_RESPONSE_MODEL, self.response_model - ) - - if self.service_tier: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, - self.service_tier, - ) - - # Set token usage span attributes - if self.input_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens - ) - if self.output_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_USAGE_OUTPUT_TOKENS, - self.output_tokens, - ) - - # Record metrics using accumulated data - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - - # Create mock result object with accumulated data for metrics - class MockResult: - def __init__( - self, - response_id, - response_model, - service_tier, - input_tokens, - output_tokens, - ): - self.id = response_id - self.model = response_model - self.service_tier = service_tier - if input_tokens > 0 or output_tokens > 0: - self.usage = type( - "Usage", - (), - { - "input_tokens": input_tokens, - "output_tokens": output_tokens, - "prompt_tokens": input_tokens, - "completion_tokens": output_tokens, - }, - )() - - mock_result = MockResult( - self.response_id, - self.response_model, - self.service_tier, - self.input_tokens, - self.output_tokens, - ) - - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=mock_result, - span_attributes=span_attributes, - ) - - # End span with proper status - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - def __iter__(self): - # Start streaming iteration - return self - - def __next__(self): - try: - chunk = next(self.stream_iter) - # Process chunk to accumulate data and maintain API compatibility - self.process_chunk(chunk) - # Also maintain backward compatibility with old accumulated_content - if hasattr(chunk, "output") and chunk.output: - self.accumulated_content.append(str(chunk.output)) - elif hasattr(chunk, "delta") and isinstance(chunk.delta, str): - self.accumulated_content.append(chunk.delta) - return chunk - except StopIteration: - # Stream is finished, perform cleanup - self.cleanup() - raise - except Exception as e: - # Error occurred, record metrics and set error status - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - self.span_ended = True - raise - - def _finalize_span(self): - """Finalize the span with accumulated content and end it.""" - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - if self.span.span_instance.is_recording: - # Note: For streaming responses, response metadata like tokens, finish_reasons - # are typically not available in individual chunks, so we focus on content. - - if self.accumulated_content: - full_content = "".join(self.accumulated_content) - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=full_content, - conversation_id=self.conversation_id, - ) - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - try: - self.cleanup() - except Exception: - pass # Don't let cleanup exceptions mask the original exception - return False - - def get_final_response(self): - """Proxy method to access the underlying stream's get_final_response if available.""" - if hasattr(self.stream_iter, "get_final_response"): - return self.stream_iter.get_final_response() - raise AttributeError("Underlying stream does not have 'get_final_response' method") - - return StreamWrapper( - stream, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ) - - def _wrap_response_stream_manager( - self, - stream_manager, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap a ResponseStreamManager to trace the stream when it's entered.""" - conversation_id = self._extract_conversation_id(original_kwargs) - instrumentor = self - - class ResponseStreamManagerWrapper: - """Wrapper for ResponseStreamManager that adds tracing to the underlying stream.""" - - def __init__( - self, - manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.manager = manager - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - self.wrapped_stream = None - - def __enter__(self): - # Enter the underlying ResponseStreamManager to get the ResponseStream - raw_stream = self.manager.__enter__() - # Wrap the ResponseStream with our tracing wrapper - self.wrapped_stream = self.instrumentor._wrap_streaming_response( - raw_stream, - self.span, - ({"conversation": self.conversation_id} if self.conversation_id else {}), - self.start_time, - self.operation_name, - self.server_address, - self.port, - self.model, - ) - return self.wrapped_stream - - def __exit__(self, exc_type, exc_val, exc_tb): - # Exit the underlying ResponseStreamManager - result = self.manager.__exit__(exc_type, exc_val, exc_tb) - return result - - return ResponseStreamManagerWrapper( - stream_manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ) - - def _wrap_async_streaming_response( - self, - stream, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap an async streaming response to trace chunks.""" - conversation_id = self._extract_conversation_id(original_kwargs) - - class AsyncStreamWrapper: # pylint: disable=too-many-instance-attributes,protected-access - def __init__( - self, - stream_async_iter, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.stream_async_iter = stream_async_iter - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.accumulated_content = [] - self.span_ended = False - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - - # Enhanced properties for sophisticated chunk processing - self.accumulated_output = [] - self.response_id = None - self.response_model = None - self.service_tier = None - self.input_tokens = 0 - self.output_tokens = 0 - self.finish_reason = None # Track finish_reason from streaming chunks - - # Track all output items from streaming events (tool calls, workflow actions, etc.) - # Use (id, type) as key to avoid overwriting when call and output have same ID - self.output_items = {} # Dict[(item_id, item_type), output_item] - self.has_output_items = False - - # Expose response attribute for compatibility with AsyncResponseStreamManager - self.response = getattr(stream_async_iter, "response", None) or getattr( - stream_async_iter, "_response", None - ) - - def append_output_content(self, content): - """Append content to accumulated output list.""" - if content: - self.accumulated_output.append(str(content)) - - def set_response_metadata(self, chunk): - """Update response metadata from chunk if not already set.""" - chunk_type = getattr(chunk, "type", None) - - if not self.response_id: - self.response_id = getattr(chunk, "id", None) - if not self.response_model: - self.response_model = getattr(chunk, "model", None) - if not self.service_tier: - self.service_tier = getattr(chunk, "service_tier", None) - - # Extract finish_reason from response.output_item.done events - if chunk_type == "response.output_item.done" and hasattr(chunk, "item"): - item = chunk.item - if hasattr(item, "status") and item.status: - self.finish_reason = item.status - # Also check for direct finish_reason attribute - elif hasattr(chunk, "finish_reason") and chunk.finish_reason: - self.finish_reason = chunk.finish_reason - # Also check for finish_details in output items (Azure AI Agents structure) - elif hasattr(chunk, "output") and chunk.output: - if isinstance(chunk.output, list) and len(chunk.output) > 0: - output_item = chunk.output[0] - if hasattr(output_item, "finish_details") and output_item.finish_details: - if hasattr(output_item.finish_details, "type"): - self.finish_reason = output_item.finish_details.type - - def process_chunk(self, chunk): - """Process chunk to accumulate data and update metadata.""" - # Check for output item events in streaming - chunk_type = getattr(chunk, "type", None) - - # Collect all complete output items from ResponseOutputItemDoneEvent or ResponseOutputItemAddedEvent - # This includes function_call, file_search_tool_call, code_interpreter_tool_call, - # web_search, mcp_call, computer_tool_call, custom_tool_call, workflow_action, and any future types - if (chunk_type in ("response.output_item.done", "response.output_item.added")) and hasattr( - chunk, "item" - ): - item = chunk.item - item_type = getattr(item, "type", None) - - # Collect any output item (tool calls, workflow actions, etc.) - if item_type: - # Use call_id, action_id, or id as the key (workflow actions use action_id) - item_id = ( - getattr(item, "call_id", None) - or getattr(item, "action_id", None) - or getattr(item, "id", None) - ) - if item_id: - # Use (id, type) tuple as key to distinguish call from output - self.output_items[(item_id, item_type)] = item - self.has_output_items = True - # Items without ID or type are skipped - - # Capture response ID from ResponseCreatedEvent or ResponseCompletedEvent - if chunk_type == "response.created" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - self.response_model = getattr(chunk.response, "model", None) - elif chunk_type == "response.completed" and hasattr(chunk, "response"): - if not self.response_id: - self.response_id = chunk.response.id - if not self.response_model: - self.response_model = getattr(chunk.response, "model", None) - # Extract usage from the completed response - if hasattr(chunk.response, "usage"): - response_usage = chunk.response.usage - if hasattr(response_usage, "input_tokens") and response_usage.input_tokens: - self.input_tokens = response_usage.input_tokens - if hasattr(response_usage, "output_tokens") and response_usage.output_tokens: - self.output_tokens = response_usage.output_tokens - # Also handle standard token field names for compatibility - if hasattr(response_usage, "prompt_tokens") and response_usage.prompt_tokens: - self.input_tokens = response_usage.prompt_tokens - if hasattr(response_usage, "completion_tokens") and response_usage.completion_tokens: - self.output_tokens = response_usage.completion_tokens - - # Only append TEXT content from delta events (not function call arguments or other deltas) - # Text deltas can come as: - # 1. response.text.delta - has delta as string - # 2. response.output_item.delta - has delta.text attribute - # Function call arguments come via response.function_call_arguments.delta - has delta as JSON string - # We need to avoid appending function call arguments - if chunk_type and ".delta" in chunk_type and hasattr(chunk, "delta"): - # If it's function_call_arguments.delta, skip it - if "function_call_arguments" not in chunk_type: - # Check if delta is a string (text content) or has .text attribute - if isinstance(chunk.delta, str): - self.append_output_content(chunk.delta) - elif hasattr(chunk.delta, "text"): - self.append_output_content(chunk.delta.text) - - # Always update metadata - self.set_response_metadata(chunk) - - # Handle usage info - usage = getattr(chunk, "usage", None) - if usage: - if hasattr(usage, "input_tokens") and usage.input_tokens: - self.input_tokens += usage.input_tokens - if hasattr(usage, "output_tokens") and usage.output_tokens: - self.output_tokens += usage.output_tokens - # Also handle standard token field names - if hasattr(usage, "prompt_tokens") and usage.prompt_tokens: - self.input_tokens += usage.prompt_tokens - if hasattr(usage, "completion_tokens") and usage.completion_tokens: - self.output_tokens += usage.completion_tokens - - def cleanup(self): - """Perform final cleanup when streaming is complete.""" - if not self.span_ended: - duration = time.time() - self.start_time - - # Join all accumulated output content - complete_content = "".join(self.accumulated_output) - - if self.span.span_instance.is_recording: - # Add tool call events if we detected any output items (tool calls, etc.) - if self.has_output_items: - # Create mock response with output items for event generation - # The existing _add_tool_call_events method handles all tool types - mock_response = type( - "Response", - (), - {"output": list(self.output_items.values())}, - )() - self.instrumentor._add_tool_call_events( - self.span, - mock_response, - self.conversation_id, - ) - # Also add workflow action events - self.instrumentor._add_workflow_action_events( - self.span, - mock_response, - self.conversation_id, - ) - - # Only add assistant message event if there's actual text content (not empty/whitespace) - if complete_content and complete_content.strip(): - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=complete_content, - conversation_id=self.conversation_id, - finish_reason=self.finish_reason, - ) - - # Set final span attributes using accumulated metadata - if self.response_id: - self.instrumentor._set_span_attribute_safe(self.span, GEN_AI_RESPONSE_ID, self.response_id) - if self.response_model: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_RESPONSE_MODEL, self.response_model - ) - - if self.service_tier: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_OPENAI_RESPONSE_SERVICE_TIER, - self.service_tier, - ) - - # Set token usage span attributes - if self.input_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, GEN_AI_USAGE_INPUT_TOKENS, self.input_tokens - ) - if self.output_tokens > 0: - self.instrumentor._set_span_attribute_safe( - self.span, - GEN_AI_USAGE_OUTPUT_TOKENS, - self.output_tokens, - ) - - # Record metrics using accumulated data - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - - # Create mock result object with accumulated data for metrics - class MockResult: - def __init__( - self, - response_id, - response_model, - service_tier, - input_tokens, - output_tokens, - ): - self.id = response_id - self.model = response_model - self.service_tier = service_tier - if input_tokens > 0 or output_tokens > 0: - self.usage = type( - "Usage", - (), - { - "input_tokens": input_tokens, - "output_tokens": output_tokens, - "prompt_tokens": input_tokens, - "completion_tokens": output_tokens, - }, - )() - - mock_result = MockResult( - self.response_id, - self.response_model, - self.service_tier, - self.input_tokens, - self.output_tokens, - ) - - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=mock_result, - span_attributes=span_attributes, - ) - - # End span with proper status - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - def __aiter__(self): - return self - - async def __anext__(self): - try: - chunk = await self.stream_async_iter.__anext__() - # Process chunk to accumulate data and maintain API compatibility - self.process_chunk(chunk) - # Also maintain backward compatibility with old accumulated_content - if hasattr(chunk, "output") and chunk.output: - self.accumulated_content.append(str(chunk.output)) - elif hasattr(chunk, "delta") and isinstance(chunk.delta, str): - self.accumulated_content.append(chunk.delta) - return chunk - except StopAsyncIteration: - # Stream is finished, perform cleanup - self.cleanup() - raise - except Exception as e: - # Error occurred, record metrics and set error status - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - if self.span.span_instance.is_recording: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - self.span_ended = True - raise - - def _finalize_span(self): - """Finalize the span with accumulated content and end it.""" - if not self.span_ended: - duration = time.time() - self.start_time - span_attributes = { - GEN_AI_REQUEST_MODEL: self.model, - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="responses", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - if self.span.span_instance.is_recording: - # Note: For streaming responses, response metadata like tokens, finish_reasons - # are typically not available in individual chunks, so we focus on content. - - if self.accumulated_content: - full_content = "".join(self.accumulated_content) - self.instrumentor._add_message_event( - self.span, - role="assistant", - content=full_content, - conversation_id=self.conversation_id, - ) - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - self.span_ended = True - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc_val, exc_tb): - try: - self.cleanup() - except Exception: - pass # Don't let cleanup exceptions mask the original exception - return False - - async def get_final_response(self): - """Proxy method to access the underlying stream's get_final_response if available.""" - if hasattr(self.stream_async_iter, "get_final_response"): - result = self.stream_async_iter.get_final_response() - # If it's a coroutine, await it - if hasattr(result, "__await__"): - return await result - return result - raise AttributeError("Underlying stream does not have 'get_final_response' method") - - return AsyncStreamWrapper( - stream, - span, - conversation_id, - self, - start_time, - operation_name, - server_address, - port, - model, - ) - - def _wrap_async_response_stream_manager( - self, - stream_manager, - span: "AbstractSpan", - original_kwargs: Dict[str, Any], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - model: Optional[str], - ): - """Wrap an AsyncResponseStreamManager to trace the stream when it's entered.""" - conversation_id = self._extract_conversation_id(original_kwargs) - instrumentor = self - - class AsyncResponseStreamManagerWrapper: - """Wrapper for AsyncResponseStreamManager that adds tracing to the underlying stream.""" - - def __init__( - self, - manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ): - self.manager = manager - self.span = span - self.conversation_id = conversation_id - self.instrumentor = instrumentor - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - self.model = model - self.wrapped_stream = None - - async def __aenter__(self): - # Enter the underlying AsyncResponseStreamManager to get the AsyncResponseStream - raw_stream = await self.manager.__aenter__() - # Wrap the AsyncResponseStream with our tracing wrapper - self.wrapped_stream = self.instrumentor._wrap_async_streaming_response( - raw_stream, - self.span, - ({"conversation": self.conversation_id} if self.conversation_id else {}), - self.start_time, - self.operation_name, - self.server_address, - self.port, - self.model, - ) - return self.wrapped_stream - - async def __aexit__(self, exc_type, exc_val, exc_tb): - # Exit the underlying AsyncResponseStreamManager - result = await self.manager.__aexit__(exc_type, exc_val, exc_tb) - return result - - return AsyncResponseStreamManagerWrapper( - stream_manager, - span, - conversation_id, - instrumentor, - start_time, - operation_name, - server_address, - port, - model, - ) - - def start_create_conversation_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - ) -> "Optional[AbstractSpan]": - """Start a span for create conversation API call.""" - span = start_span( - operation_name=OperationName.CREATE_CONVERSATION, - server_address=server_address, - port=port, - span_name=OperationName.CREATE_CONVERSATION.value, - gen_ai_provider=RESPONSES_PROVIDER, - ) - - if span and span.span_instance.is_recording: - self._set_span_attribute_safe(span, GEN_AI_OPERATION_NAME, OperationName.CREATE_CONVERSATION.value) - - return span - - def _create_conversations_span_from_parameters(self, *args, **kwargs): # pylint: disable=unused-argument - """Extract parameters and create span for conversations API tracing.""" - # Extract client from args (first argument) - client = args[0] if args else None - server_address, port = self._extract_server_info_from_client(client) - - # Create and return the span - return self.start_create_conversation_span( - server_address=server_address, - port=port, - ) - - def trace_conversations_create(self, function, *args, **kwargs): - """Trace synchronous conversations.create calls.""" - span = self._create_conversations_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "create_conversation" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - with span: - try: - result = function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set conversation attributes - self._extract_conversation_attributes(span, result) - - # Record metrics using new dedicated method - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - - async def trace_conversations_create_async(self, function, *args, **kwargs): - """Trace asynchronous conversations.create calls.""" - span = self._create_conversations_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "create_conversation" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - with span: - try: - result = await function(*args, **kwargs) - duration = time.time() - start_time - - # Extract and set conversation attributes - self._extract_conversation_attributes(span, result) - - # Record metrics using new dedicated method - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=result, - span_attributes=span_attributes, - ) - - return result - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - span.span_instance.record_exception(e) - raise - - def start_list_conversation_items_span( - self, - server_address: Optional[str] = None, - port: Optional[int] = None, - conversation_id: Optional[str] = None, - ) -> "Optional[AbstractSpan]": - """Start a span for list conversation items API call.""" - span = start_span( - operation_name=OperationName.LIST_CONVERSATION_ITEMS, - server_address=server_address, - port=port, - span_name=OperationName.LIST_CONVERSATION_ITEMS.value, - gen_ai_provider=RESPONSES_PROVIDER, - ) - - if span and span.span_instance.is_recording: - # Set operation name attribute (start_span doesn't set this automatically) - self._set_attributes( - span, - (GEN_AI_OPERATION_NAME, OperationName.LIST_CONVERSATION_ITEMS.value), - ) - - # Set conversation-specific attributes that start_span doesn't handle - # Note: server_address is already set by start_span, so we don't need to set it again - self._set_span_attribute_safe(span, GEN_AI_CONVERSATION_ID, conversation_id) - - return span - - def _add_conversation_item_event( # pylint: disable=too-many-branches,too-many-locals - self, - span: "AbstractSpan", - item: Any, - ) -> None: - """Add a conversation item event to the span.""" - if not span or not span.span_instance.is_recording: - return - - # Extract basic item information - item_id = getattr(item, "id", None) - item_type = getattr(item, "type", "unknown") - role = getattr(item, "role", None) - - # Create event body - format depends on item type - event_body: List[Dict[str, Any]] = [] - - # Declare tool_call variable with type for use across branches - tool_call: Dict[str, Any] - - # Handle different item types - if item_type == "function_call_output": - # Function tool output - use optimized content format - role = "tool" # Override role for tool outputs - - tool_output: Dict[str, Any] = { - "type": item_type, - } - - # Add call_id as "id" - always include for correlation - if hasattr(item, "call_id"): - tool_output["id"] = item.call_id - elif hasattr(item, "id"): - tool_output["id"] = item.id - - # Add output field only if content recording is enabled - if _trace_responses_content: - if hasattr(item, "output"): - output_value = item.output - if isinstance(output_value, str): - try: - tool_output["output"] = json.loads(output_value) - except (json.JSONDecodeError, TypeError): - tool_output["output"] = output_value - else: - tool_output["output"] = output_value - - # Always include role and parts with type/id, only output when content recording enabled - event_body = [ - { - "role": role, - "parts": [{"type": "tool_call_output", "content": tool_output}], - } - ] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "function_call": - # Function tool call - use optimized content format - role = "assistant" # Override role for function calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include function details if content recording is enabled - if _trace_responses_content: - # Add function details - if hasattr(item, "name"): - function_details: Dict[str, Any] = { - "name": item.name, - } - if hasattr(item, "arguments"): - # Parse arguments if it's a JSON string - args_value = item.arguments - if isinstance(args_value, str): - try: - function_details["arguments"] = json.loads(args_value) - except (json.JSONDecodeError, TypeError): - function_details["arguments"] = args_value - else: - function_details["arguments"] = args_value - - tool_call["function"] = function_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "file_search_call": - # File search tool call - role = "assistant" # Override role for file search calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include file search details if content recording is enabled - if _trace_responses_content: - # Add file search details - file_search_details: Dict[str, Any] = {} - - if hasattr(item, "queries") and item.queries: - file_search_details["queries"] = item.queries - - if hasattr(item, "status"): - file_search_details["status"] = item.status - - if hasattr(item, "results") and item.results: - file_search_details["results"] = [ - { - "file_id": getattr(result, "file_id", None), - "file_name": getattr(result, "file_name", None), - "score": getattr(result, "score", None), - } - for result in item.results - ] - - if file_search_details: - tool_call["file_search"] = file_search_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "code_interpreter_call": - # Code interpreter tool call - role = "assistant" # Override role for code interpreter calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include code interpreter details if content recording is enabled - if _trace_responses_content: - # Add code interpreter details - code_interpreter_details: Dict[str, Any] = {} - - if hasattr(item, "code") and item.code: - code_interpreter_details["code"] = item.code - - if hasattr(item, "status"): - code_interpreter_details["status"] = item.status - - if hasattr(item, "outputs") and item.outputs: - outputs_list = [] - for output in item.outputs: - output_type = getattr(output, "type", None) - if output_type == "logs": - outputs_list.append({"type": "logs", "logs": getattr(output, "logs", None)}) - elif output_type == "image": - # Use consistent "content" field for image data - outputs_list.append( - { - "type": "image", - "content": { - "file_id": getattr( - getattr(output, "image", None), - "file_id", - None, - ) - }, - } - ) - if outputs_list: - code_interpreter_details["outputs"] = outputs_list - - if code_interpreter_details: - tool_call["code_interpreter"] = code_interpreter_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "web_search_call": - # Web search tool call - role = "assistant" # Override role for web search calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include web search details if content recording is enabled - if _trace_responses_content: - # Add web search details - web_search_details: Dict[str, Any] = {} - - if hasattr(item, "status"): - web_search_details["status"] = item.status - - if hasattr(item, "action") and item.action: - action_type = getattr(item.action, "type", None) - web_search_details["action_type"] = action_type - - if action_type == "search" and hasattr(item.action, "query"): - web_search_details["query"] = item.action.query - elif action_type == "open_page" and hasattr(item.action, "url"): - web_search_details["url"] = item.action.url - elif action_type == "find" and hasattr(item.action, "query"): - web_search_details["find_query"] = item.action.query - - if web_search_details: - tool_call["web_search"] = web_search_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "azure_ai_search_call": - # Azure AI Search tool call - role = "assistant" # Override role for Azure AI Search calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include Azure AI Search details if content recording is enabled - if _trace_responses_content: - # Add Azure AI Search details - azure_ai_search_details: Dict[str, Any] = {} - - if hasattr(item, "status"): - azure_ai_search_details["status"] = item.status - - if hasattr(item, "input"): - azure_ai_search_details["input"] = item.input - - if hasattr(item, "results") and item.results: - azure_ai_search_details["results"] = [] - for result in item.results: - result_data = {} - if hasattr(result, "title"): - result_data["title"] = result.title - if hasattr(result, "url"): - result_data["url"] = result.url - if hasattr(result, "content"): - result_data["content"] = result.content - if result_data: - azure_ai_search_details["results"].append(result_data) - - if azure_ai_search_details: - tool_call["azure_ai_search"] = azure_ai_search_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "image_generation_call": - # Image generation tool call - role = "assistant" # Override role for image generation calls - - tool_call = { - "type": item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "call_id"): - tool_call["id"] = item.call_id - elif hasattr(item, "id"): - tool_call["id"] = item.id - - # Only include image generation details if content recording is enabled - if _trace_responses_content: - # Add image generation details - image_gen_details: Dict[str, Any] = {} - - if hasattr(item, "prompt"): - image_gen_details["prompt"] = item.prompt - - if hasattr(item, "quality"): - image_gen_details["quality"] = item.quality - - if hasattr(item, "size"): - image_gen_details["size"] = item.size - - if hasattr(item, "style"): - image_gen_details["style"] = item.style - - # Include the result (image data) only if binary data tracing is enabled - if _trace_binary_data and hasattr(item, "result") and item.result: - image_gen_details["result"] = item.result - - if image_gen_details: - tool_call["image_generation"] = image_gen_details - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "remote_function_call": - # Remote function call (like Bing Custom Search call) - role = "assistant" # Override role for remote function calls - - # Check if there's a more specific type in name field (e.g., "bing_custom_search_preview_call") - specific_type = None - if hasattr(item, "name") and item.name: - # Use the API type directly without transformation - specific_type = item.name - - tool_call = { - "type": specific_type if specific_type else item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "id"): - tool_call["id"] = item.id - elif hasattr(item, "call_id"): - tool_call["id"] = item.call_id - # Check model_extra for call_id - elif hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - if "call_id" in item.model_extra: - tool_call["id"] = item.model_extra["call_id"] - - # Only include tool details if content recording is enabled - if _trace_responses_content: - # Extract data from model_extra if available (Pydantic v2 style) - if hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - for key, value in item.model_extra.items(): - # Skip already captured fields, redundant fields (name, label), internal fields (partition_key), and empty/None values - if ( - key not in ["type", "id", "call_id", "name", "label", "partition_key"] - and value is not None - and value != "" - ): - tool_call[key] = value - - # Also try as_dict if available - if hasattr(item, "as_dict"): - try: - tool_dict = item.as_dict() - # Extract relevant fields (exclude already captured ones and empty/None values) - for key, value in tool_dict.items(): - if key not in [ - "type", - "id", - "call_id", - "name", - "label", - "role", - "content", - ]: - # Skip empty strings and None values - if value is not None and value != "": - # Don't overwrite if already exists - if key not in tool_call: - tool_call[key] = value - except Exception as e: - logger.debug(f"Failed to extract data from as_dict: {e}") - - # Fallback: try common fields directly (skip if empty and skip redundant name/label) - for field in [ - "input", - "arguments", - "status", - "error", - "search_query", - "query", - ]: - if hasattr(item, field): - try: - value = getattr(item, field) - if value is not None and value != "": - # If not already in tool_call, add it - if field not in tool_call: - tool_call[field] = value - except Exception: - pass - - # Include role in content for semantic convention compliance - event_body = [{"role": role, "parts": [{"type": "tool_call", "content": tool_call}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "remote_function_call_output": - # Remote function call output (like Bing Custom Search output) - role = "tool" # Tool outputs use role "tool" - - # Check if there's a more specific type in name field (e.g., "bing_custom_search_preview_call_output") - specific_type = None - if hasattr(item, "name") and item.name: - # Use the API type directly without transformation - specific_type = item.name - - tool_output = { - "type": specific_type if specific_type else item_type, - } - - # Always include ID (needed for correlation) - if hasattr(item, "id"): - tool_output["id"] = item.id - elif hasattr(item, "call_id"): - tool_output["id"] = item.call_id - # Check model_extra for call_id - elif hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - if "call_id" in item.model_extra: - tool_output["id"] = item.model_extra["call_id"] - - # Only include tool details if content recording is enabled - if _trace_responses_content: - # Extract data from model_extra if available (Pydantic v2 style) - if hasattr(item, "model_extra") and isinstance(item.model_extra, dict): - for key, value in item.model_extra.items(): - # Skip already captured fields, redundant fields (name, label), internal fields (partition_key), and empty/None values - if ( - key not in ["type", "id", "call_id", "name", "label", "partition_key"] - and value is not None - and value != "" - ): - tool_output[key] = value - - # Also try as_dict if available - if hasattr(item, "as_dict"): - try: - tool_dict = item.as_dict() - # Extract relevant fields (exclude already captured ones and empty/None values) - for key, value in tool_dict.items(): - if key not in [ - "type", - "id", - "call_id", - "name", - "label", - "role", - "content", - ]: - # Skip empty strings and None values - if value is not None and value != "": - # Don't overwrite if already exists - if key not in tool_output: - tool_output[key] = value - except Exception as e: - logger.debug(f"Failed to extract data from as_dict: {e}") - - # Fallback: try common fields directly (skip if empty and skip redundant name/label) - for field in [ - "input", - "output", - "results", - "status", - "error", - "search_query", - "query", - ]: - if hasattr(item, field): - try: - value = getattr(item, field) - if value is not None and value != "": - # If not already in tool_output, add it - if field not in tool_output: - tool_output[field] = value - except Exception: - pass - - # Tool outputs use tool_call_output type in parts - event_body = [{"role": role, "parts": [{"type": "tool_call_output", "content": tool_output}]}] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "workflow_action": - # Workflow action item - include workflow execution details - role = "workflow" - - # Extract workflow action attributes - status = getattr(item, "status", None) - - # Build workflow action details object - workflow_details: Dict[str, Any] = {} - - if status: - workflow_details["status"] = status - - # Only include action_id and previous_action_id when content recording is enabled - if _trace_responses_content: - action_id = getattr(item, "action_id", None) - previous_action_id = getattr(item, "previous_action_id", None) - - if action_id: - workflow_details["action_id"] = action_id - if previous_action_id: - workflow_details["previous_action_id"] = previous_action_id - - # Wrap in parts array for semantic convention compliance - parts: List[Dict[str, Any]] = [{"type": "workflow_action", "content": workflow_details}] - event_body = [{"role": role, "parts": parts}] - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - elif item_type == "message": - # Regular message - use content format for consistency - parts = [] - - # Always inspect content to determine types, regardless of recording setting - if hasattr(item, "content") and item.content: - for content_item in item.content: - content_type = getattr(content_item, "type", None) - - if content_type in ("input_text", "output_text", "text"): - if _trace_responses_content and hasattr(content_item, "text"): - # Include actual text content when recording is enabled - parts.append({"type": "text", "content": content_item.text}) - else: - # Type-only when recording is disabled - parts.append({"type": "text"}) - elif content_type == "input_image": - # Handle image content - image_part = {"type": "image"} - # Include image data if binary data tracing is enabled - # Note: The API typically doesn't return image_url in conversation items list, - # only in the original responses.create call - if _trace_binary_data: - image_url = getattr(content_item, "image_url", None) - if image_url: - # Use consistent format: content field directly contains the URL - image_part["content"] = image_url - parts.append(image_part) - elif content_type == "input_file": - # Handle file content - file_part: Dict[str, Any] = {"type": "file"} - file_content_dict: Dict[str, Any] = {} - filename = getattr(content_item, "filename", None) - if filename: - file_content_dict["filename"] = filename - file_id = getattr(content_item, "file_id", None) - if file_id: - file_content_dict["file_id"] = file_id - # Include file data if binary data tracing is enabled - if _trace_binary_data: - file_data = getattr(content_item, "file_data", None) - if file_data: - file_content_dict["file_data"] = file_data - if file_content_dict: - file_part["content"] = file_content_dict - parts.append(file_part) - - # Always create event_body with role and parts - role_obj: Dict[str, Any] = {"role": role} - if parts: - role_obj["parts"] = parts - - event_body = [role_obj] - - # Use conversation item event for all message items during listing - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - elif item_type and item_type.startswith("mcp"): - # MCP-specific item types (mcp_approval_request, mcp_list_tools, mcp_call, etc.) - # Determine role based on whether it's a response (user) or request/call (assistant) - if "response" in item_type: - # MCP responses (e.g., mcp_approval_response) are user inputs - mcp_role = "user" - else: - # MCP requests/calls (e.g., mcp_approval_request, mcp_list_tools, mcp_call) are assistant-initiated - mcp_role = "assistant" - - # Create structured event body - mcp_tool_call: Dict[str, Any] = { - "type": item_type, - } - - # Always include ID if available - if hasattr(item, "id"): - mcp_tool_call["id"] = item.id - elif hasattr(item, "call_id"): - mcp_tool_call["id"] = item.call_id - elif hasattr(item, "approval_request_id"): - # For approval responses, use the request ID - mcp_tool_call["id"] = item.approval_request_id - - # Only include additional details if content recording is enabled - if _trace_responses_content: - # Try to capture common MCP fields - for field in [ - "name", - "server_label", - "arguments", - "approval_request_id", - "approve", - "status", - ]: - if hasattr(item, field): - value = getattr(item, field) - if value is not None: - mcp_tool_call[field] = value - - # Wrap in parts array with appropriate role - event_body = [{"role": mcp_role, "parts": [{"type": "mcp", "content": mcp_tool_call}]}] - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - else: - # Unknown item type - create minimal event body with role if available - # This handles MCP tools and other future item types - else_role_obj: Dict[str, Any] = {} - if role: - else_role_obj["role"] = role - event_body = [else_role_obj] if else_role_obj else [] - - event_name = GEN_AI_CONVERSATION_ITEM_EVENT - - # Create event attributes - event_attributes = { - GEN_AI_PROVIDER_NAME: RESPONSES_PROVIDER, - GEN_AI_CONVERSATION_ITEM_ID: item_id, - } - - # Commented out - message_role is now included in the event content instead - # # Add role attribute if present - # if role is not None: - # event_attributes[GEN_AI_CONVERSATION_ITEM_ROLE] = role - - # Use JSON format for event content (consistent with responses.create) - event_attributes[GEN_AI_EVENT_CONTENT] = json.dumps(event_body, ensure_ascii=False) - - span.span_instance.add_event(name=event_name, attributes=event_attributes) - - def _wrap_conversation_items_list( - self, - result, - span: Optional["AbstractSpan"], - start_time: float, - operation_name: str, - server_address: Optional[str], - port: Optional[int], - ): - """Wrap the conversation items list result to add events for each item.""" - - class ItemsWrapper: - def __init__( - self, - items_result, - span, - instrumentor, - start_time, - operation_name, - server_address, - port, - ): - self.items_result = items_result - self.span = span - self.instrumentor = instrumentor - self.start_time = start_time - self.operation_name = operation_name - self.server_address = server_address - self.port = port - - def __iter__(self): - # For synchronous iteration - try: - for item in self.items_result: - if self.span: - self.instrumentor._add_conversation_item_event(self.span, item) - yield item - - # Record metrics when iteration is complete - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - # End span when iteration is complete - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - except Exception as e: - # Record metrics for error case - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - raise - - async def __aiter__(self): - # For asynchronous iteration - try: - async for item in self.items_result: - if self.span: - self.instrumentor._add_conversation_item_event(self.span, item) - yield item - - # Record metrics when iteration is complete - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - ) - - # End span when iteration is complete - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.OK - ) - self.span.span_instance.end() - except Exception as e: - # Record metrics for error case - duration = time.time() - self.start_time - span_attributes = { - SERVER_ADDRESS: self.server_address, - SERVER_PORT: self.port, - } - self.instrumentor._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - - if self.span: - self.span.span_instance.set_status( - # pyright: ignore [reportPossiblyUnboundVariable] - StatusCode.ERROR, - str(e), - ) - self.span.span_instance.record_exception(e) - self.span.span_instance.end() - raise - - def __getattr__(self, name): - # Delegate other attributes to the original result - return getattr(self.items_result, name) - - return ItemsWrapper(result, span, self, start_time, operation_name, server_address, port) - - def _create_list_conversation_items_span_from_parameters(self, *args, **kwargs): - """Extract parameters and create span for list conversation items API tracing.""" - # Extract client from args (first argument) - client = args[0] if args else None - server_address, port = self._extract_server_info_from_client(client) - - # Extract conversation_id from kwargs - conversation_id = kwargs.get("conversation_id") - - return self.start_list_conversation_items_span( - server_address=server_address, - port=port, - conversation_id=conversation_id, - ) - - def trace_list_conversation_items(self, function, *args, **kwargs): - """Trace synchronous conversations.items.list calls.""" - span = self._create_list_conversation_items_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "list_conversation_items" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = function(*args, **kwargs) - # For list operations, we can't measure duration until iteration is complete - # So we'll record metrics in the wrapper or during iteration - return self._wrap_conversation_items_list( - result, None, start_time, operation_name, server_address, port - ) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Don't use context manager since we need the span to stay open during iteration - try: - result = function(*args, **kwargs) - - # Extract and set conversation items attributes - self._extract_conversation_items_attributes(span, result, args, kwargs) - - # Wrap the result to add events during iteration and handle span ending - wrapped_result = self._wrap_conversation_items_list( - result, span, start_time, operation_name, server_address, port - ) - - return wrapped_result - - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.ERROR, str(e)) - span.span_instance.record_exception(e) - span.span_instance.end() - raise - - async def trace_list_conversation_items_async(self, function, *args, **kwargs): - """Trace asynchronous conversations.items.list calls.""" - span = self._create_list_conversation_items_span_from_parameters(*args, **kwargs) - - # Extract parameters for metrics - server_address, port = self._extract_server_info_from_client(args[0] if args else None) - operation_name = "list_conversation_items" - - start_time = time.time() - - if span is None: - # Still record metrics even without spans - try: - result = await function(*args, **kwargs) - # For list operations, we can't measure duration until iteration is complete - # So we'll record metrics in the wrapper or during iteration - return self._wrap_conversation_items_list( - result, None, start_time, operation_name, server_address, port - ) - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - raise - - # Don't use context manager since we need the span to stay open during iteration - try: - result = await function(*args, **kwargs) - - # Extract and set conversation items attributes - self._extract_conversation_items_attributes(span, result, args, kwargs) - - # Wrap the result to add events during iteration and handle span ending - wrapped_result = self._wrap_conversation_items_list( - result, span, start_time, operation_name, server_address, port - ) - - return wrapped_result - - except Exception as e: - duration = time.time() - start_time - span_attributes = { - SERVER_ADDRESS: server_address, - SERVER_PORT: port, - } - self._record_metrics( - operation_type="conversation_items", - duration=duration, - result=None, - span_attributes=span_attributes, - error_type=str(type(e).__name__), - ) - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.ERROR, str(e)) - span.span_instance.record_exception(e) - span.span_instance.end() - raise - - def _trace_sync_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.RESPONSES, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to a synchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.RESPONSES. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - def inner(*args, **kwargs): - if _name == "create" and _trace_type == TraceType.RESPONSES: - return self.trace_responses_create(function, *args, **kwargs) - if _name == "stream" and _trace_type == TraceType.RESPONSES: - return self.trace_responses_stream(function, *args, **kwargs) - if _name == "create" and _trace_type == TraceType.CONVERSATIONS: - return self.trace_conversations_create(function, *args, **kwargs) - if _name == "list" and _trace_type == TraceType.CONVERSATIONS: - return self.trace_list_conversation_items(function, *args, **kwargs) - - return function(*args, **kwargs) - - return inner - - def _trace_async_function( - self, - function: Callable, - *, - _args_to_ignore: Optional[List[str]] = None, - _trace_type=TraceType.RESPONSES, - _name: Optional[str] = None, - ) -> Callable: - """ - Decorator that adds tracing to an asynchronous function. - - :param function: The function to be traced. - :type function: Callable - :param args_to_ignore: A list of argument names to be ignored in the trace. Defaults to None. - :type: args_to_ignore: [List[str]], optional - :param trace_type: The type of the trace. Defaults to TraceType.RESPONSES. - :type trace_type: TraceType, optional - :param name: The name of the trace, will set to func name if not provided. - :type name: str, optional - :return: The traced function. - :rtype: Callable - """ - - @functools.wraps(function) - async def inner(*args, **kwargs): - if _name == "create" and _trace_type == TraceType.RESPONSES: - return await self.trace_responses_create_async(function, *args, **kwargs) - if _name == "stream" and _trace_type == TraceType.RESPONSES: - # stream() is not async, just returns async context manager, so don't await - return self.trace_responses_stream_async(function, *args, **kwargs) - if _name == "create" and _trace_type == TraceType.CONVERSATIONS: - return await self.trace_conversations_create_async(function, *args, **kwargs) - if _name == "list" and _trace_type == TraceType.CONVERSATIONS: - return await self.trace_list_conversation_items_async(function, *args, **kwargs) - - return await function(*args, **kwargs) - - return inner - - def _inject_async(self, f, _trace_type, _name): - wrapper_fun = self._trace_async_function(f, _trace_type=_trace_type, _name=_name) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _inject_sync(self, f, _trace_type, _name): - wrapper_fun = self._trace_sync_function(f, _trace_type=_trace_type, _name=_name) - wrapper_fun._original = f # pylint: disable=protected-access # pyright: ignore [reportFunctionMemberAccess] - return wrapper_fun - - def _responses_apis(self): - sync_apis = [] - async_apis = [] - - try: - import openai.resources.responses as responses_module - - if hasattr(responses_module, "Responses"): - sync_apis.append( - ( - responses_module.Responses, - "create", - TraceType.RESPONSES, - self._inject_sync, - "create", - ) - ) - # Add stream method - sync_apis.append( - ( - responses_module.Responses, - "stream", - TraceType.RESPONSES, - self._inject_sync, - "stream", - ) - ) - except ImportError: - pass - - try: - import openai.resources.responses as responses_module - - if hasattr(responses_module, "AsyncResponses"): - async_apis.append( - ( - responses_module.AsyncResponses, - "create", - TraceType.RESPONSES, - self._inject_async, - "create", - ) - ) - # Add stream method - note: stream() is not async, just returns async context manager - # So we use _inject_sync even though it's on AsyncResponses - sync_apis.append( - ( - responses_module.AsyncResponses, - "stream", - TraceType.RESPONSES, - self._inject_sync, - "stream", - ) - ) - except ImportError: - pass - - return sync_apis, async_apis - - def _conversations_apis(self): - sync_apis = [] - async_apis = [] - - try: - from openai.resources.conversations.conversations import Conversations - - sync_apis.append( - ( - Conversations, - "create", - TraceType.CONVERSATIONS, - self._inject_sync, - "create", - ) - ) - except ImportError: - pass - - try: - from openai.resources.conversations.conversations import AsyncConversations - - async_apis.append( - ( - AsyncConversations, - "create", - TraceType.CONVERSATIONS, - self._inject_async, - "create", - ) - ) - except ImportError: - pass - - # Add conversation items APIs - try: - from openai.resources.conversations.items import Items - - sync_apis.append( - ( - Items, - "list", - TraceType.CONVERSATIONS, - self._inject_sync, - "list", - ) - ) - except ImportError: - pass - - try: - from openai.resources.conversations.items import AsyncItems - - async_apis.append( - ( - AsyncItems, - "list", - TraceType.CONVERSATIONS, - self._inject_async, - "list", - ) - ) - except ImportError: - pass - - return sync_apis, async_apis - - def _responses_api_list(self): - sync_apis, async_apis = self._responses_apis() - yield from sync_apis - yield from async_apis - - def _conversations_api_list(self): - sync_apis, async_apis = self._conversations_apis() - yield from sync_apis - yield from async_apis - - def _all_api_list(self): - yield from self._responses_api_list() - yield from self._conversations_api_list() - - def _generate_api_and_injector(self, apis): - yield from apis - - def _available_responses_apis_and_injectors(self): - """ - Generates a sequence of tuples containing Responses and Conversations API classes, method names, and - corresponding injector functions. - - :return: A generator yielding tuples. - :rtype: tuple - """ - yield from self._generate_api_and_injector(self._all_api_list()) - - def _instrument_responses(self, enable_content_tracing: bool = False, enable_binary_data: bool = False): - """This function modifies the methods of the Responses API classes to - inject logic before calling the original methods. - The original methods are stored as _original attributes of the methods. - - :param enable_content_tracing: Indicates whether tracing of message content should be enabled. - This also controls whether function call tool function names, - parameter names and parameter values are traced. - :type enable_content_tracing: bool - :param enable_binary_data: Indicates whether tracing of binary data (such as images) should be enabled. - This only takes effect when content recording is also enabled. - :type enable_binary_data: bool - """ - # pylint: disable=W0603 - global _responses_traces_enabled - global _trace_responses_content - global _trace_binary_data - if _responses_traces_enabled: - return - - _responses_traces_enabled = True - _trace_responses_content = enable_content_tracing - _trace_binary_data = enable_binary_data - - # Initialize metrics instruments - self._initialize_metrics() - - for ( - api, - method, - trace_type, - injector, - name, - ) in self._available_responses_apis_and_injectors(): - try: - setattr(api, method, injector(getattr(api, method), trace_type, name)) - except (AttributeError, ImportError) as e: - logger.debug(f"Could not instrument {api.__name__}.{method}: {e}") - - def _uninstrument_responses(self): - global _responses_traces_enabled - global _trace_responses_content - if not _responses_traces_enabled: - return - - _responses_traces_enabled = False - _trace_responses_content = False - for ( - api, - method, - trace_type, - injector, - name, - ) in self._available_responses_apis_and_injectors(): - try: - original_method = getattr(getattr(api, method), "_original", None) - if original_method: - setattr(api, method, original_method) - except (AttributeError, ImportError): - pass - - def _is_instrumented(self): - global _responses_traces_enabled - return _responses_traces_enabled - - def _set_enable_content_recording(self, enable_content_recording: bool = False) -> None: - global _trace_responses_content - _trace_responses_content = enable_content_recording - - def _is_content_recording_enabled(self) -> bool: - global _trace_responses_content - return _trace_responses_content - - def _set_enable_binary_data(self, enable_binary_data: bool = False) -> None: - global _trace_binary_data - _trace_binary_data = enable_binary_data - - def _is_binary_data_enabled(self) -> bool: - global _trace_binary_data - return _trace_binary_data - - def record_error(self, span, exc): - # pyright: ignore [reportPossiblyUnboundVariable] - span.span_instance.set_status(StatusCode.ERROR, str(exc)) - span.span_instance.record_exception(exc) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py deleted file mode 100644 index 956b43792d71..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_trace_function.py +++ /dev/null @@ -1,206 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -import functools -import inspect -from typing import Any, Callable, Optional, Dict - -try: - # pylint: disable = no-name-in-module - from opentelemetry import trace as opentelemetry_trace - - _tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] - _tracing_library_available = True -except ModuleNotFoundError: - _tracing_library_available = False - -if _tracing_library_available: - - def trace_function(span_name: Optional[str] = None): - """ - A decorator for tracing function calls using OpenTelemetry. - - This decorator handles various data types for function parameters and return values, - and records them as attributes in the trace span. The supported data types include: - - Basic data types: str, int, float, bool - - Collections: list, dict, tuple, set - - Special handling for collections: - - If a collection (list, dict, tuple, set) contains nested collections, the entire collection - is converted to a string before being recorded as an attribute. - - Sets and dictionaries are always converted to strings to ensure compatibility with span attributes. - - Object types are omitted, and the corresponding parameter is not traced. - - :param span_name: The name of the span. If not provided, the function name is used. - :type span_name: Optional[str] - :return: The decorated function with tracing enabled. - :rtype: Callable - """ - - def decorator(func: Callable) -> Callable: - @functools.wraps(func) - async def async_wrapper(*args: Any, **kwargs: Any) -> Any: - """ - Wrapper function for asynchronous functions. - - :param args: Positional arguments passed to the function. - :type args: Tuple[Any] - :return: The result of the decorated asynchronous function. - :rtype: Any - """ - tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] - name = span_name if span_name else func.__name__ - with tracer.start_as_current_span(name) as span: - try: - # Sanitize parameters and set them as attributes - sanitized_params = sanitize_parameters(func, *args, **kwargs) - span.set_attributes(sanitized_params) - result = await func(*args, **kwargs) - sanitized_result = sanitize_for_attributes(result) - if sanitized_result is not None: - if isinstance(sanitized_result, (list, dict, tuple, set)): - if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_result): - sanitized_result = str(sanitized_result) - span.set_attribute("code.function.return.value", sanitized_result) # type: ignore - return result - except Exception as e: - span.record_exception(e) - span.set_attribute("error.type", e.__class__.__qualname__) # type: ignore - raise - - @functools.wraps(func) - def sync_wrapper(*args: Any, **kwargs: Any) -> Any: - """ - Wrapper function for synchronous functions. - - :param args: Positional arguments passed to the function. - :type args: Tuple[Any] - :return: The result of the decorated synchronous function. - :rtype: Any - """ - tracer = opentelemetry_trace.get_tracer(__name__) # type: ignore[attr-defined] - name = span_name if span_name else func.__name__ - with tracer.start_as_current_span(name) as span: - try: - # Sanitize parameters and set them as attributes - sanitized_params = sanitize_parameters(func, *args, **kwargs) - span.set_attributes(sanitized_params) - result = func(*args, **kwargs) - sanitized_result = sanitize_for_attributes(result) - if sanitized_result is not None: - if isinstance(sanitized_result, (list, dict, tuple, set)): - if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_result): - sanitized_result = str(sanitized_result) - span.set_attribute("code.function.return.value", sanitized_result) # type: ignore - return result - except Exception as e: - span.record_exception(e) - span.set_attribute("error.type", e.__class__.__qualname__) # type: ignore - raise - - # Determine if the function is async - if inspect.iscoroutinefunction(func): - return async_wrapper - return sync_wrapper - - return decorator - -else: - # Define a no-op decorator if OpenTelemetry is not available - def trace_function(span_name: Optional[str] = None): # pylint: disable=unused-argument - """ - A no-op decorator for tracing function calls when OpenTelemetry is not available. - - :param span_name: Not used in this version. - :type span_name: Optional[str] - :return: The original function. - :rtype: Callable - """ - - def decorator(func: Callable) -> Callable: - return func - - return decorator - - -def sanitize_parameters(func, *args, **kwargs) -> Dict[str, Any]: - """ - Sanitize function parameters to include only built-in data types. - - :param func: The function being decorated. - :type func: Callable - :param args: Positional arguments passed to the function. - :type args: Tuple[Any] - :return: A dictionary of sanitized parameters. - :rtype: Dict[str, Any] - """ - params = inspect.signature(func).parameters - sanitized_params = {} - - for i, (name, param) in enumerate(params.items()): - if i < len(args): - # Use positional argument if provided - value = args[i] - else: - # Use keyword argument if provided, otherwise fall back to default value - value = kwargs.get(name, param.default) - - sanitized_value = sanitize_for_attributes(value) - # Check if the collection has nested collections - if isinstance(sanitized_value, (list, dict, tuple, set)): - if any(isinstance(i, (list, dict, tuple, set)) for i in sanitized_value): - sanitized_value = str(sanitized_value) - if sanitized_value is not None: - sanitized_params["code.function.parameter." + name] = sanitized_value - - return sanitized_params - - -# pylint: disable=R0911 -def sanitize_for_attributes(value: Any, is_recursive: bool = False) -> Any: - """ - Sanitize a value to be used as an attribute. - - :param value: The value to sanitize. - :type value: Any - :param is_recursive: Indicates if the function is being called recursively. Default is False. - :type is_recursive: bool - :return: The sanitized value or None if the value is not a supported type. - :rtype: Any - """ - if isinstance(value, (str, int, float, bool)): - return value - if isinstance(value, list): - return [ - sanitize_for_attributes(item, True) - for item in value - if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) - ] - if isinstance(value, dict): - retval = { - k: sanitize_for_attributes(v, True) - for k, v in value.items() - if isinstance(v, (str, int, float, bool, list, dict, tuple, set)) - } - # dict to compatible with span attribute, so return it as a string - if is_recursive: - return retval - return str(retval) - if isinstance(value, tuple): - return tuple( - sanitize_for_attributes(item, True) - for item in value - if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) - ) - if isinstance(value, set): - retval_set = { - sanitize_for_attributes(item, True) - for item in value - if isinstance(item, (str, int, float, bool, list, dict, tuple, set)) - } - if is_recursive: - return retval_set - return str(retval_set) - return None diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py b/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py deleted file mode 100644 index 931c3d2abf7b..000000000000 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/telemetry/_utils.py +++ /dev/null @@ -1,278 +0,0 @@ -# pylint: disable=line-too-long,useless-suppression -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -from typing import Optional -import logging -from enum import Enum - -from azure.core.tracing import AbstractSpan, SpanKind # type: ignore -from azure.core.settings import settings # type: ignore - -try: - _span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable -except ModuleNotFoundError: - _span_impl_type = None - -logger = logging.getLogger(__name__) - - -GEN_AI_MESSAGE_ID = "gen_ai.message.id" -GEN_AI_MESSAGE_STATUS = "gen_ai.message.status" -GEN_AI_THREAD_ID = "gen_ai.thread.id" -GEN_AI_THREAD_RUN_ID = "gen_ai.thread.run.id" -GEN_AI_AGENT_ID = "gen_ai.agent.id" -GEN_AI_AGENT_NAME = "gen_ai.agent.name" -GEN_AI_AGENT_DESCRIPTION = "gen_ai.agent.description" -GEN_AI_OPERATION_NAME = "gen_ai.operation.name" -GEN_AI_THREAD_RUN_STATUS = "gen_ai.thread.run.status" -GEN_AI_REQUEST_MODEL = "gen_ai.request.model" -GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature" -GEN_AI_REQUEST_TOP_P = "gen_ai.request.top_p" -GEN_AI_REQUEST_MAX_INPUT_TOKENS = "gen_ai.request.max_input_tokens" -GEN_AI_REQUEST_MAX_OUTPUT_TOKENS = "gen_ai.request.max_output_tokens" -GEN_AI_RESPONSE_MODEL = "gen_ai.response.model" -GEN_AI_SYSTEM = "gen_ai.system" -SERVER_ADDRESS = "server.address" -SERVER_PORT = "server.port" -AZ_AI_AGENT_SYSTEM = "az.ai.agents" -AZURE_AI_AGENTS = "azure.ai.agents" -AZ_NAMESPACE = "az.namespace" -AZ_NAMESPACE_VALUE = "Microsoft.CognitiveServices" -GEN_AI_TOOL_NAME = "gen_ai.tool.name" -GEN_AI_TOOL_CALL_ID = "gen_ai.tool.call.id" -GEN_AI_REQUEST_RESPONSE_FORMAT = "gen_ai.request.response_format" -GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens" -GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens" -GEN_AI_SYSTEM_MESSAGE = "gen_ai.system_instructions" -GEN_AI_EVENT_CONTENT = "gen_ai.event.content" -GEN_AI_RUN_STEP_START_TIMESTAMP = "gen_ai.run_step.start.timestamp" -GEN_AI_RUN_STEP_END_TIMESTAMP = "gen_ai.run_step.end.timestamp" -GEN_AI_RUN_STEP_STATUS = "gen_ai.run_step.status" -ERROR_TYPE = "error.type" -ERROR_MESSAGE = "error.message" -GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION = "1.34.0" -GEN_AI_PROVIDER_NAME = "gen_ai.provider.name" - -# Added to the latest version, not part of semantic conventions -GEN_AI_REQUEST_REASONING_EFFORT = "gen_ai.request.reasoning.effort" -GEN_AI_REQUEST_REASONING_SUMMARY = "gen_ai.request.reasoning.summary" -GEN_AI_REQUEST_STRUCTURED_INPUTS = "gen_ai.request.structured_inputs" -GEN_AI_AGENT_VERSION = "gen_ai.agent.version" - -# Additional attribute names -GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id" -GEN_AI_CONVERSATION_ITEM_ID = "gen_ai.conversation.item.id" -GEN_AI_CONVERSATION_ITEM_ROLE = "gen_ai.conversation.item.role" -GEN_AI_REQUEST_TOOLS = "gen_ai.request.tools" -GEN_AI_RESPONSE_ID = "gen_ai.response.id" -GEN_AI_OPENAI_RESPONSE_SYSTEM_FINGERPRINT = "gen_ai.openai.response.system_fingerprint" -GEN_AI_OPENAI_RESPONSE_SERVICE_TIER = "gen_ai.openai.response.service_tier" -GEN_AI_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens" -GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons" -GEN_AI_RESPONSE_OBJECT = "gen_ai.response.object" -GEN_AI_TOKEN_TYPE = "gen_ai.token.type" -GEN_AI_MESSAGE_ROLE = "gen_ai.message.role" -GEN_AI_AGENT_TYPE = "gen_ai.agent.type" -GEN_AI_CONVERSATION_ITEM_TYPE = "gen_ai.conversation.item.type" -GEN_AI_AGENT_HOSTED_CPU = "gen_ai.agent.hosted.cpu" -GEN_AI_AGENT_HOSTED_MEMORY = "gen_ai.agent.hosted.memory" -GEN_AI_AGENT_HOSTED_IMAGE = "gen_ai.agent.hosted.image" -GEN_AI_AGENT_HOSTED_PROTOCOL = "gen_ai.agent.hosted.protocol" -GEN_AI_AGENT_HOSTED_PROTOCOL_VERSION = "gen_ai.agent.hosted.protocol_version" - -# Event names -GEN_AI_USER_MESSAGE_EVENT = "gen_ai.input.messages" -GEN_AI_ASSISTANT_MESSAGE_EVENT = "gen_ai.output.messages" -GEN_AI_TOOL_MESSAGE_EVENT = "gen_ai.input.messages" # Keep separate constant but use same value as user messages -GEN_AI_WORKFLOW_ACTION_EVENT = "gen_ai.workflow.action" -GEN_AI_CONVERSATION_ITEM_EVENT = "gen_ai.conversation.item" -GEN_AI_SYSTEM_INSTRUCTION_EVENT = "gen_ai.system.instructions" -GEN_AI_AGENT_WORKFLOW_EVENT = "gen_ai.agent.workflow" - -# Attribute names for messages (when USE_MESSAGE_EVENTS = False) -GEN_AI_INPUT_MESSAGES = "gen_ai.input.messages" -GEN_AI_OUTPUT_MESSAGES = "gen_ai.output.messages" - -# Metric names -GEN_AI_CLIENT_OPERATION_DURATION = "gen_ai.client.operation.duration" -GEN_AI_CLIENT_TOKEN_USAGE = "gen_ai.client.token.usage" - -# Constant attribute values -AZURE_AI_AGENTS_SYSTEM = "az.ai.agents" -AGENTS_PROVIDER = "microsoft.foundry" -RESPONSES_PROVIDER = "microsoft.foundry" -AGENT_TYPE_PROMPT = "prompt" -AGENT_TYPE_WORKFLOW = "workflow" -AGENT_TYPE_HOSTED = "hosted" -AGENT_TYPE_UNKNOWN = "unknown" - -# Span name prefixes for responses API operations -SPAN_NAME_INVOKE_AGENT = "invoke_agent" -SPAN_NAME_CHAT = "chat" - -# Operation names for gen_ai.operation.name attribute -OPERATION_NAME_INVOKE_AGENT = "invoke_agent" -OPERATION_NAME_CHAT = "chat" - -# Configuration: Controls whether input/output messages are emitted as events or attributes -# Can be set at runtime for testing purposes (internal use only) -# Set to True for event-based, False for attribute-based (default) -_use_message_events = False - -# Configuration: Controls whether function tool calls use simplified OTEL-compliant format -# When True (default), function_call and function_call_output use a simpler structure: -# tool_call: {"type": "tool_call", "id": "...", "name": "...", "arguments": {...}} -# tool_call_response: {"type": "tool_call_response", "id": "...", "result": "..."} -# When False, the full nested structure is used -_use_simple_tool_format = True - - -def _get_use_simple_tool_format() -> bool: - """Get the current tool format mode (simple vs nested). Internal use only. - - :return: True if using simple OTEL-compliant format, False if using nested format - :rtype: bool - """ - return _use_simple_tool_format - - -def _set_use_simple_tool_format(use_simple: bool) -> None: - """ - Set the tool format mode at runtime. Internal use only. - - :param use_simple: True to use simple OTEL-compliant format, False for nested format - :type use_simple: bool - """ - global _use_simple_tool_format # pylint: disable=global-statement - _use_simple_tool_format = use_simple - - -def _get_use_message_events() -> bool: - """Get the current message tracing mode (events vs attributes). Internal use only. - - :return: True if using events, False if using attributes - :rtype: bool - """ - return _use_message_events - - -def _set_use_message_events(use_events: bool) -> None: - """ - Set the message tracing mode at runtime. Internal use only. - - :param use_events: True to use events (default), False to use attributes - :type use_events: bool - """ - global _use_message_events # pylint: disable=global-statement - _use_message_events = use_events - - -class OperationName(Enum): - CREATE_AGENT = "create_agent" - CREATE_THREAD = "create_thread" - CREATE_MESSAGE = "create_message" - START_THREAD_RUN = "start_thread_run" - GET_THREAD_RUN = "get_thread_run" - EXECUTE_TOOL = "execute_tool" - LIST_MESSAGES = "list_messages" - LIST_RUN_STEPS = "list_run_steps" - SUBMIT_TOOL_OUTPUTS = "submit_tool_outputs" - PROCESS_THREAD_RUN = "process_thread_run" - RESPONSES = "responses" - CREATE_CONVERSATION = "create_conversation" - LIST_CONVERSATION_ITEMS = "list_conversation_items" - - -def start_span( - operation_name: OperationName, - server_address: Optional[str], - port: Optional[int] = None, - span_name: Optional[str] = None, - thread_id: Optional[str] = None, - agent_id: Optional[str] = None, - run_id: Optional[str] = None, - model: Optional[str] = None, - temperature: Optional[float] = None, - top_p: Optional[float] = None, - max_prompt_tokens: Optional[int] = None, - max_completion_tokens: Optional[int] = None, - response_format: Optional[str] = None, - reasoning: Optional[str] = None, # pylint: disable=unused-argument - reasoning_effort: Optional[str] = None, - reasoning_summary: Optional[str] = None, - structured_inputs: Optional[str] = None, - gen_ai_system: Optional[str] = None, - gen_ai_provider: Optional[str] = AGENTS_PROVIDER, - kind: SpanKind = SpanKind.CLIENT, -) -> "Optional[AbstractSpan]": - global _span_impl_type # pylint: disable=global-statement - if _span_impl_type is None: - # Try to reinitialize the span implementation type. - # This is a workaround for the case when the tracing implementation is not set up yet when the agent telemetry is imported. - # This code should not even get called if settings.tracing_implementation() returns None since that is also checked in - # _trace_sync_function and _trace_async_function functions in the AIProjectInstrumentor. - _span_impl_type = settings.tracing_implementation() # pylint: disable=not-callable - if _span_impl_type is None: - return None - - span = _span_impl_type( - name=span_name or operation_name.value, - kind=kind, - schema_version=GEN_AI_SEMANTIC_CONVENTIONS_SCHEMA_VERSION, - ) - - if span and span.span_instance.is_recording: - span.add_attribute(AZ_NAMESPACE, AZ_NAMESPACE_VALUE) - span.add_attribute(GEN_AI_PROVIDER_NAME, AGENTS_PROVIDER) - - if gen_ai_provider: - span.add_attribute(GEN_AI_PROVIDER_NAME, gen_ai_provider) - - if gen_ai_system: - span.add_attribute(GEN_AI_SYSTEM, gen_ai_system) - - if server_address: - span.add_attribute(SERVER_ADDRESS, server_address) - - if port is not None and port != 443: - span.add_attribute(SERVER_PORT, port) - - if thread_id: - span.add_attribute(GEN_AI_THREAD_ID, thread_id) - - if agent_id: - span.add_attribute(GEN_AI_AGENT_ID, agent_id) - - if run_id: - span.add_attribute(GEN_AI_THREAD_RUN_ID, run_id) - - if model: - span.add_attribute(GEN_AI_REQUEST_MODEL, model) - - if temperature: - span.add_attribute(GEN_AI_REQUEST_TEMPERATURE, str(temperature)) - - if top_p: - span.add_attribute(GEN_AI_REQUEST_TOP_P, str(top_p)) - - if max_prompt_tokens: - span.add_attribute(GEN_AI_REQUEST_MAX_INPUT_TOKENS, max_prompt_tokens) - - if max_completion_tokens: - span.add_attribute(GEN_AI_REQUEST_MAX_OUTPUT_TOKENS, max_completion_tokens) - - if response_format: - span.add_attribute(GEN_AI_REQUEST_RESPONSE_FORMAT, response_format) - - if reasoning_effort: - span.add_attribute(GEN_AI_REQUEST_REASONING_EFFORT, reasoning_effort) - - if reasoning_summary: - span.add_attribute(GEN_AI_REQUEST_REASONING_SUMMARY, reasoning_summary) - - if structured_inputs: - span.add_attribute(GEN_AI_REQUEST_STRUCTURED_INPUTS, structured_inputs) - - return span diff --git a/sdk/ai/azure-ai-projects/pyproject.toml b/sdk/ai/azure-ai-projects/pyproject.toml index 26eac48e1123..fe16fe054548 100644 --- a/sdk/ai/azure-ai-projects/pyproject.toml +++ b/sdk/ai/azure-ai-projects/pyproject.toml @@ -14,10 +14,10 @@ name = "azure-ai-projects" authors = [ { name = "Microsoft Corporation", email = "azpysdkhelp@microsoft.com" }, ] -description = "Microsoft Corporation Azure AI Projects Client Library for Python" +description = "Microsoft Corporation Azure Ai Projects Client Library for Python" license = "MIT" classifiers = [ - "Development Status :: 5 - Production/Stable", + "Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3", @@ -26,7 +26,6 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", - "Programming Language :: Python :: 3.14", ] requires-python = ">=3.9" keywords = ["azure", "azure sdk"] @@ -44,7 +43,7 @@ dynamic = [ ] [project.urls] -repository = "https://aka.ms/azsdk/azure-ai-projects-v2/python/code" +repository = "https://github.com/Azure/azure-sdk-for-python" [tool.setuptools.dynamic] version = {attr = "azure.ai.projects._version.VERSION"} @@ -52,13 +51,13 @@ readme = {file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown"} [tool.setuptools.packages.find] exclude = [ - "azure.ai", - "azure", - "doc*", - "generated_samples*", + "tests*", "generated_tests*", "samples*", - "tests*", + "generated_samples*", + "doc*", + "azure", + "azure.ai", ] [tool.setuptools.package-data] @@ -69,4 +68,3 @@ verifytypes = false [tool.azure-sdk-conda] in_bundle = false - diff --git a/sdk/ai/azure-ai-projects/tsp-location.yaml b/sdk/ai/azure-ai-projects/tsp-location.yaml index 01ecabb49ebf..a029139a710b 100644 --- a/sdk/ai/azure-ai-projects/tsp-location.yaml +++ b/sdk/ai/azure-ai-projects/tsp-location.yaml @@ -1,4 +1,4 @@ directory: specification/ai-foundry/data-plane/Foundry -commit: e50321286a093e4cd6c77edfa7162d73d142f594 +commit: bd70f1fbb6f1691a3107d791c7aa5d1dede46f01 repo: Azure/azure-rest-api-specs additionalDirectories: