azure-ai-evaluation 1.0.0b5__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of azure-ai-evaluation might be problematic. Click here for more details.
- azure/ai/evaluation/_azure/__init__.py +3 -0
- azure/ai/evaluation/_azure/_clients.py +188 -0
- azure/ai/evaluation/_azure/_models.py +227 -0
- azure/ai/evaluation/_azure/_token_manager.py +118 -0
- azure/ai/evaluation/_common/_experimental.py +4 -0
- azure/ai/evaluation/_common/math.py +62 -2
- azure/ai/evaluation/_common/rai_service.py +110 -50
- azure/ai/evaluation/_common/utils.py +50 -16
- azure/ai/evaluation/_constants.py +2 -0
- azure/ai/evaluation/_evaluate/_batch_run/eval_run_context.py +9 -0
- azure/ai/evaluation/_evaluate/_batch_run/proxy_client.py +13 -3
- azure/ai/evaluation/_evaluate/_batch_run/target_run_context.py +12 -1
- azure/ai/evaluation/_evaluate/_eval_run.py +38 -43
- azure/ai/evaluation/_evaluate/_evaluate.py +62 -131
- azure/ai/evaluation/_evaluate/_telemetry/__init__.py +2 -1
- azure/ai/evaluation/_evaluate/_utils.py +72 -38
- azure/ai/evaluation/_evaluators/_bleu/_bleu.py +16 -17
- azure/ai/evaluation/_evaluators/_coherence/_coherence.py +60 -29
- azure/ai/evaluation/_evaluators/_common/_base_eval.py +88 -6
- azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +16 -3
- azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py +39 -10
- azure/ai/evaluation/_evaluators/_content_safety/_content_safety.py +58 -52
- azure/ai/evaluation/_evaluators/_content_safety/_hate_unfairness.py +79 -34
- azure/ai/evaluation/_evaluators/_content_safety/_self_harm.py +73 -34
- azure/ai/evaluation/_evaluators/_content_safety/_sexual.py +74 -33
- azure/ai/evaluation/_evaluators/_content_safety/_violence.py +76 -34
- azure/ai/evaluation/_evaluators/_eci/_eci.py +28 -3
- azure/ai/evaluation/_evaluators/_f1_score/_f1_score.py +20 -13
- azure/ai/evaluation/_evaluators/_fluency/_fluency.py +57 -26
- azure/ai/evaluation/_evaluators/_gleu/_gleu.py +13 -15
- azure/ai/evaluation/_evaluators/_groundedness/_groundedness.py +68 -30
- azure/ai/evaluation/_evaluators/_meteor/_meteor.py +17 -20
- azure/ai/evaluation/_evaluators/_multimodal/_content_safety_multimodal.py +10 -8
- azure/ai/evaluation/_evaluators/_multimodal/_content_safety_multimodal_base.py +0 -2
- azure/ai/evaluation/_evaluators/_multimodal/_hate_unfairness.py +6 -2
- azure/ai/evaluation/_evaluators/_multimodal/_protected_material.py +10 -6
- azure/ai/evaluation/_evaluators/_multimodal/_self_harm.py +6 -2
- azure/ai/evaluation/_evaluators/_multimodal/_sexual.py +6 -2
- azure/ai/evaluation/_evaluators/_multimodal/_violence.py +6 -2
- azure/ai/evaluation/_evaluators/_protected_material/_protected_material.py +57 -34
- azure/ai/evaluation/_evaluators/_qa/_qa.py +25 -37
- azure/ai/evaluation/_evaluators/_relevance/_relevance.py +63 -29
- azure/ai/evaluation/_evaluators/_retrieval/_retrieval.py +76 -161
- azure/ai/evaluation/_evaluators/_rouge/_rouge.py +24 -25
- azure/ai/evaluation/_evaluators/_service_groundedness/_service_groundedness.py +65 -67
- azure/ai/evaluation/_evaluators/_similarity/_similarity.py +26 -20
- azure/ai/evaluation/_evaluators/_xpia/xpia.py +74 -40
- azure/ai/evaluation/_exceptions.py +2 -0
- azure/ai/evaluation/_http_utils.py +6 -4
- azure/ai/evaluation/_model_configurations.py +65 -14
- azure/ai/evaluation/_vendor/rouge_score/rouge_scorer.py +0 -4
- azure/ai/evaluation/_vendor/rouge_score/scoring.py +0 -4
- azure/ai/evaluation/_vendor/rouge_score/tokenize.py +0 -4
- azure/ai/evaluation/_version.py +1 -1
- azure/ai/evaluation/simulator/_adversarial_scenario.py +17 -1
- azure/ai/evaluation/simulator/_adversarial_simulator.py +57 -47
- azure/ai/evaluation/simulator/_constants.py +11 -1
- azure/ai/evaluation/simulator/_conversation/__init__.py +128 -7
- azure/ai/evaluation/simulator/_conversation/_conversation.py +0 -1
- azure/ai/evaluation/simulator/_direct_attack_simulator.py +16 -8
- azure/ai/evaluation/simulator/_indirect_attack_simulator.py +12 -1
- azure/ai/evaluation/simulator/_model_tools/_identity_manager.py +3 -1
- azure/ai/evaluation/simulator/_model_tools/_rai_client.py +48 -4
- azure/ai/evaluation/simulator/_model_tools/_template_handler.py +1 -0
- azure/ai/evaluation/simulator/_simulator.py +54 -45
- azure/ai/evaluation/simulator/_utils.py +25 -7
- {azure_ai_evaluation-1.0.0b5.dist-info → azure_ai_evaluation-1.1.0.dist-info}/METADATA +240 -327
- {azure_ai_evaluation-1.0.0b5.dist-info → azure_ai_evaluation-1.1.0.dist-info}/RECORD +71 -68
- azure/ai/evaluation/_evaluators/_content_safety/_content_safety_chat.py +0 -322
- {azure_ai_evaluation-1.0.0b5.dist-info → azure_ai_evaluation-1.1.0.dist-info}/NOTICE.txt +0 -0
- {azure_ai_evaluation-1.0.0b5.dist-info → azure_ai_evaluation-1.1.0.dist-info}/WHEEL +0 -0
- {azure_ai_evaluation-1.0.0b5.dist-info → azure_ai_evaluation-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# ---------------------------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
# ---------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
from logging import Logger
|
|
6
|
+
from typing import Any, Dict, Final, Optional, Set, Union, cast
|
|
7
|
+
from threading import Lock
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
from json.decoder import JSONDecodeError
|
|
10
|
+
|
|
11
|
+
from azure.core.credentials import TokenCredential, AzureSasCredential
|
|
12
|
+
from azure.core.rest import HttpResponse
|
|
13
|
+
from azure.ai.evaluation._exceptions import ErrorBlame, ErrorCategory, ErrorTarget, EvaluationException
|
|
14
|
+
from azure.ai.evaluation._http_utils import HttpPipeline, get_http_client
|
|
15
|
+
from azure.ai.evaluation._azure._token_manager import AzureMLTokenManager
|
|
16
|
+
from azure.ai.evaluation.simulator._model_tools._identity_manager import TokenScope
|
|
17
|
+
from ._models import BlobStoreInfo, Workspace
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
API_VERSION: Final[str] = "2024-10-01"
|
|
21
|
+
QUERY_KEY_API_VERSION: Final[str] = "api-version"
|
|
22
|
+
PATH_ML_WORKSPACES = ("providers", "Microsoft.MachineLearningServices", "workspaces")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LiteMLClient:
|
|
26
|
+
"""A lightweight Azure ML API client.
|
|
27
|
+
|
|
28
|
+
:param subscription_id: Azure subscription ID
|
|
29
|
+
:type subscription_id: str
|
|
30
|
+
:param resource_group: Azure resource group name
|
|
31
|
+
:type resource_group: str
|
|
32
|
+
:param logger: Logger object
|
|
33
|
+
:type logger: logging.Logger
|
|
34
|
+
:keyword credential: Azure credentials
|
|
35
|
+
:paramtype credential: TokenCredential
|
|
36
|
+
:keyword kwargs: Additional keyword arguments
|
|
37
|
+
:paramtype kwargs: Dict
|
|
38
|
+
:keyword str api_version: The API version. Default is 2024-10-01
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
subscription_id: str,
|
|
44
|
+
resource_group: str,
|
|
45
|
+
logger: Logger,
|
|
46
|
+
credential: Optional[TokenCredential] = None,
|
|
47
|
+
**kwargs: Any,
|
|
48
|
+
) -> None:
|
|
49
|
+
subscription_id = quote(subscription_id, safe="")
|
|
50
|
+
resource_group = quote(resource_group, safe="")
|
|
51
|
+
|
|
52
|
+
self._base_url: Final[str] = (
|
|
53
|
+
f"https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resource_group}"
|
|
54
|
+
)
|
|
55
|
+
self._logger: Final[Logger] = logger
|
|
56
|
+
self._api_version: Final[str] = kwargs.get("api_version", API_VERSION)
|
|
57
|
+
self._http_client: Final[HttpPipeline] = get_http_client(**kwargs)
|
|
58
|
+
self._lock: Final[Lock] = Lock()
|
|
59
|
+
|
|
60
|
+
# things that can change under lock
|
|
61
|
+
self._token_manager: Optional[AzureMLTokenManager] = None
|
|
62
|
+
self._credential: Optional[TokenCredential] = credential
|
|
63
|
+
|
|
64
|
+
def get_token(self) -> str:
|
|
65
|
+
return self._get_token_manager().get_token()
|
|
66
|
+
|
|
67
|
+
def get_credential(self) -> TokenCredential:
|
|
68
|
+
# load the token manager to get the credential if needed
|
|
69
|
+
self._get_token_manager()
|
|
70
|
+
return cast(TokenCredential, self._credential)
|
|
71
|
+
|
|
72
|
+
def workspace_get_default_datastore(self, workspace_name: str, include_credentials: bool = False) -> BlobStoreInfo:
|
|
73
|
+
# 1. Get the default blob store
|
|
74
|
+
# REST API documentation:
|
|
75
|
+
# https://learn.microsoft.com/rest/api/azureml/datastores/list?view=rest-azureml-2024-10-01
|
|
76
|
+
url = self._generate_path( # pylint: disable=specify-parameter-names-in-call
|
|
77
|
+
*PATH_ML_WORKSPACES, workspace_name, "datastores"
|
|
78
|
+
)
|
|
79
|
+
headers = self._get_headers()
|
|
80
|
+
|
|
81
|
+
stores_response = self._http_client.request(
|
|
82
|
+
method="GET",
|
|
83
|
+
url=url,
|
|
84
|
+
params={QUERY_KEY_API_VERSION: self._api_version, "isDefault": True, "count": 1, "orderByAsc": "false"},
|
|
85
|
+
headers=headers,
|
|
86
|
+
)
|
|
87
|
+
self._throw_on_http_error(stores_response, "list default workspace datastore")
|
|
88
|
+
|
|
89
|
+
json = stores_response.json()["value"][0]
|
|
90
|
+
props_json = json["properties"]
|
|
91
|
+
name = json["name"]
|
|
92
|
+
account_name = props_json["accountName"]
|
|
93
|
+
endpoint = props_json["endpoint"]
|
|
94
|
+
container_name = props_json["containerName"]
|
|
95
|
+
|
|
96
|
+
# 2. Get the SAS token to use for accessing the blob store
|
|
97
|
+
# REST API documentation:
|
|
98
|
+
# https://learn.microsoft.com/rest/api/azureml/datastores/list-secrets?view=rest-azureml-2024-10-01
|
|
99
|
+
blob_store_credential: Optional[Union[AzureSasCredential, str]] = None
|
|
100
|
+
if include_credentials:
|
|
101
|
+
url = self._generate_path(
|
|
102
|
+
*PATH_ML_WORKSPACES, workspace_name, "datastores", "workspaceblobstore", "listSecrets"
|
|
103
|
+
)
|
|
104
|
+
secrets_response = self._http_client.request(
|
|
105
|
+
method="POST",
|
|
106
|
+
url=url,
|
|
107
|
+
params={
|
|
108
|
+
QUERY_KEY_API_VERSION: self._api_version,
|
|
109
|
+
},
|
|
110
|
+
headers=headers,
|
|
111
|
+
)
|
|
112
|
+
self._throw_on_http_error(secrets_response, "workspace datastore secrets")
|
|
113
|
+
|
|
114
|
+
secrets_json = secrets_response.json()
|
|
115
|
+
secrets_type = secrets_json["secretsType"].lower()
|
|
116
|
+
|
|
117
|
+
if secrets_type == "sas":
|
|
118
|
+
blob_store_credential = AzureSasCredential(secrets_json["sasToken"])
|
|
119
|
+
elif secrets_type == "accountkey":
|
|
120
|
+
# To support olders versions of azure-storage-blob better, we return a string here instead of
|
|
121
|
+
# an AzureNamedKeyCredential
|
|
122
|
+
blob_store_credential = secrets_json["key"]
|
|
123
|
+
else:
|
|
124
|
+
raise EvaluationException(
|
|
125
|
+
message=f"The '{account_name}' blob store does not use a recognized credential type.",
|
|
126
|
+
internal_message=f"The credential type is '{secrets_type}'",
|
|
127
|
+
target=ErrorTarget.EVALUATE,
|
|
128
|
+
category=ErrorCategory.INVALID_VALUE,
|
|
129
|
+
blame=ErrorBlame.SYSTEM_ERROR,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return BlobStoreInfo(name, account_name, endpoint, container_name, blob_store_credential)
|
|
133
|
+
|
|
134
|
+
def workspace_get_info(self, workspace_name: str) -> Workspace:
|
|
135
|
+
# https://learn.microsoft.com/rest/api/azureml/workspaces/get?view=rest-azureml-2024-10-01
|
|
136
|
+
workspace_response = self._http_client.request(
|
|
137
|
+
"GET",
|
|
138
|
+
self._generate_path(*PATH_ML_WORKSPACES, workspace_name),
|
|
139
|
+
params={QUERY_KEY_API_VERSION: self._api_version},
|
|
140
|
+
headers=self._get_headers(),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
self._throw_on_http_error(workspace_response, f"get '{workspace_name}' workspace")
|
|
144
|
+
workspace = Workspace.deserialize(workspace_response)
|
|
145
|
+
return workspace
|
|
146
|
+
|
|
147
|
+
def _get_token_manager(self) -> AzureMLTokenManager:
|
|
148
|
+
# Lazy init since getting credentials in the constructor can take a long time in some situations
|
|
149
|
+
if self._token_manager is None:
|
|
150
|
+
with self._lock:
|
|
151
|
+
if self._token_manager is None:
|
|
152
|
+
self._token_manager = AzureMLTokenManager(
|
|
153
|
+
TokenScope.DEFAULT_AZURE_MANAGEMENT.value, self._logger, credential=self._credential
|
|
154
|
+
)
|
|
155
|
+
self._credential = self._token_manager.credential
|
|
156
|
+
|
|
157
|
+
return self._token_manager
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def _throw_on_http_error(response: HttpResponse, description: str, valid_status: Optional[Set[int]] = None) -> None:
|
|
161
|
+
if valid_status and (response.status_code in valid_status):
|
|
162
|
+
return
|
|
163
|
+
if response.status_code >= 200 and response.status_code < 300:
|
|
164
|
+
# nothing to see here, move along
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
additional_info: Optional[str] = None
|
|
168
|
+
try:
|
|
169
|
+
error_json = response.json()["error"]
|
|
170
|
+
additional_info = f"({error_json['code']}) {error_json['message']}"
|
|
171
|
+
except (JSONDecodeError, ValueError, KeyError):
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
raise EvaluationException(
|
|
175
|
+
message=f"The {description} request failed with HTTP {response.status_code}",
|
|
176
|
+
target=ErrorTarget.EVALUATE,
|
|
177
|
+
category=ErrorCategory.FAILED_EXECUTION,
|
|
178
|
+
blame=ErrorBlame.SYSTEM_ERROR,
|
|
179
|
+
internal_message=additional_info,
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _generate_path(self, *paths: str) -> str:
|
|
183
|
+
sanitized_paths = [quote(path, safe="") for path in paths]
|
|
184
|
+
url = self._base_url + "/" + str.join("/", sanitized_paths)
|
|
185
|
+
return url
|
|
186
|
+
|
|
187
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
188
|
+
return {"Authorization": f"Bearer {self.get_token()}", "Content-Type": "application/json"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# ---------------------------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
# ---------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
# pylint: disable=too-many-instance-attributes
|
|
6
|
+
# pylint: disable=too-many-locals
|
|
7
|
+
# pylint: disable=line-too-long
|
|
8
|
+
|
|
9
|
+
from typing import Dict, List, NamedTuple, Optional, Union
|
|
10
|
+
from msrest.serialization import Model
|
|
11
|
+
from azure.core.credentials import AzureSasCredential
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class BlobStoreInfo(NamedTuple):
|
|
15
|
+
name: str
|
|
16
|
+
account_name: str
|
|
17
|
+
endpoint: str
|
|
18
|
+
container_name: str
|
|
19
|
+
credential: Optional[Union[AzureSasCredential, str]]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkspaceHubConfig(Model):
|
|
23
|
+
"""WorkspaceHub's configuration object."""
|
|
24
|
+
|
|
25
|
+
_attribute_map = {
|
|
26
|
+
"additional_workspace_storage_accounts": {"key": "additionalWorkspaceStorageAccounts", "type": "[str]"},
|
|
27
|
+
"default_workspace_resource_group": {"key": "defaultWorkspaceResourceGroup", "type": "str"},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
additional_workspace_storage_accounts: Optional[List[str]] = None,
|
|
34
|
+
default_workspace_resource_group: Optional[str] = None,
|
|
35
|
+
**kwargs
|
|
36
|
+
):
|
|
37
|
+
super(WorkspaceHubConfig, self).__init__(**kwargs)
|
|
38
|
+
self.additional_workspace_storage_accounts = additional_workspace_storage_accounts
|
|
39
|
+
self.default_workspace_resource_group = default_workspace_resource_group
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Workspace(Model):
|
|
43
|
+
"""An object that represents a machine learning workspace.
|
|
44
|
+
|
|
45
|
+
Variables are only populated by the server, and will be ignored when sending a request."""
|
|
46
|
+
|
|
47
|
+
_validation = {
|
|
48
|
+
"id": {"readonly": True},
|
|
49
|
+
"name": {"readonly": True},
|
|
50
|
+
"type": {"readonly": True},
|
|
51
|
+
#'system_data': {'readonly': True},
|
|
52
|
+
"agents_endpoint_uri": {"readonly": True},
|
|
53
|
+
"ml_flow_tracking_uri": {"readonly": True},
|
|
54
|
+
#'notebook_info': {'readonly': True},
|
|
55
|
+
"private_endpoint_connections": {"readonly": True},
|
|
56
|
+
#'private_link_count': {'readonly': True},
|
|
57
|
+
"provisioning_state": {"readonly": True},
|
|
58
|
+
"service_provisioned_resource_group": {"readonly": True},
|
|
59
|
+
"storage_hns_enabled": {"readonly": True},
|
|
60
|
+
"tenant_id": {"readonly": True},
|
|
61
|
+
"workspace_id": {"readonly": True},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_attribute_map = {
|
|
65
|
+
"id": {"key": "id", "type": "str"},
|
|
66
|
+
"name": {"key": "name", "type": "str"},
|
|
67
|
+
"type": {"key": "type", "type": "str"},
|
|
68
|
+
#'system_data': {'key': 'systemData', 'type': 'SystemData'},
|
|
69
|
+
#'identity': {'key': 'identity', 'type': 'ManagedServiceIdentity'},
|
|
70
|
+
"kind": {"key": "kind", "type": "str"},
|
|
71
|
+
"location": {"key": "location", "type": "str"},
|
|
72
|
+
#'sku': {'key': 'sku', 'type': 'Sku'},
|
|
73
|
+
"tags": {"key": "tags", "type": "{str}"},
|
|
74
|
+
"agents_endpoint_uri": {"key": "properties.agentsEndpointUri", "type": "str"},
|
|
75
|
+
"allow_public_access_when_behind_vnet": {"key": "properties.allowPublicAccessWhenBehindVnet", "type": "bool"},
|
|
76
|
+
"allow_role_assignment_on_rg": {"key": "properties.allowRoleAssignmentOnRG", "type": "bool"},
|
|
77
|
+
"application_insights": {"key": "properties.applicationInsights", "type": "str"},
|
|
78
|
+
"associated_workspaces": {"key": "properties.associatedWorkspaces", "type": "[str]"},
|
|
79
|
+
"container_registries": {"key": "properties.containerRegistries", "type": "[str]"},
|
|
80
|
+
"container_registry": {"key": "properties.containerRegistry", "type": "str"},
|
|
81
|
+
"description": {"key": "properties.description", "type": "str"},
|
|
82
|
+
"discovery_url": {"key": "properties.discoveryUrl", "type": "str"},
|
|
83
|
+
"enable_data_isolation": {"key": "properties.enableDataIsolation", "type": "bool"},
|
|
84
|
+
"enable_service_side_cmk_encryption": {"key": "properties.enableServiceSideCMKEncryption", "type": "bool"},
|
|
85
|
+
"enable_simplified_cmk": {"key": "properties.enableSimplifiedCmk", "type": "bool"},
|
|
86
|
+
"enable_software_bill_of_materials": {"key": "properties.enableSoftwareBillOfMaterials", "type": "bool"},
|
|
87
|
+
#'encryption': {'key': 'properties.encryption', 'type': 'EncryptionProperty'},
|
|
88
|
+
"existing_workspaces": {"key": "properties.existingWorkspaces", "type": "[str]"},
|
|
89
|
+
#'feature_store_settings': {'key': 'properties.featureStoreSettings', 'type': 'FeatureStoreSettings'},
|
|
90
|
+
"friendly_name": {"key": "properties.friendlyName", "type": "str"},
|
|
91
|
+
"hbi_workspace": {"key": "properties.hbiWorkspace", "type": "bool"},
|
|
92
|
+
"hub_resource_id": {"key": "properties.hubResourceId", "type": "str"},
|
|
93
|
+
"image_build_compute": {"key": "properties.imageBuildCompute", "type": "str"},
|
|
94
|
+
"ip_allowlist": {"key": "properties.ipAllowlist", "type": "[str]"},
|
|
95
|
+
"key_vault": {"key": "properties.keyVault", "type": "str"},
|
|
96
|
+
"key_vaults": {"key": "properties.keyVaults", "type": "[str]"},
|
|
97
|
+
#'managed_network': {'key': 'properties.managedNetwork', 'type': 'ManagedNetworkSettings'},
|
|
98
|
+
"ml_flow_tracking_uri": {"key": "properties.mlFlowTrackingUri", "type": "str"},
|
|
99
|
+
#'network_acls': {'key': 'properties.networkAcls', 'type': 'NetworkAcls'},
|
|
100
|
+
#'notebook_info': {'key': 'properties.notebookInfo', 'type': 'NotebookResourceInfo'},
|
|
101
|
+
"primary_user_assigned_identity": {"key": "properties.primaryUserAssignedIdentity", "type": "str"},
|
|
102
|
+
"private_endpoint_connections": {
|
|
103
|
+
"key": "properties.privateEndpointConnections",
|
|
104
|
+
"type": "[PrivateEndpointConnection]",
|
|
105
|
+
},
|
|
106
|
+
"private_link_count": {"key": "properties.privateLinkCount", "type": "int"},
|
|
107
|
+
"provision_network_now": {"key": "properties.provisionNetworkNow", "type": "bool"},
|
|
108
|
+
"provisioning_state": {"key": "properties.provisioningState", "type": "str"},
|
|
109
|
+
#'public_network_access': {'key': 'properties.publicNetworkAccess', 'type': 'str'},
|
|
110
|
+
#'serverless_compute_settings': {'key': 'properties.serverlessComputeSettings', 'type': 'ServerlessComputeSettings'},
|
|
111
|
+
#'service_managed_resources_settings': {'key': 'properties.serviceManagedResourcesSettings', 'type': 'ServiceManagedResourcesSettings'},
|
|
112
|
+
"service_provisioned_resource_group": {"key": "properties.serviceProvisionedResourceGroup", "type": "str"},
|
|
113
|
+
#'shared_private_link_resources': {'key': 'properties.sharedPrivateLinkResources', 'type': '[SharedPrivateLinkResource]'},
|
|
114
|
+
"soft_delete_retention_in_days": {"key": "properties.softDeleteRetentionInDays", "type": "int"},
|
|
115
|
+
"storage_account": {"key": "properties.storageAccount", "type": "str"},
|
|
116
|
+
"storage_accounts": {"key": "properties.storageAccounts", "type": "[str]"},
|
|
117
|
+
"storage_hns_enabled": {"key": "properties.storageHnsEnabled", "type": "bool"},
|
|
118
|
+
#'system_datastores_auth_mode': {'key': 'properties.systemDatastoresAuthMode', 'type': 'str'},
|
|
119
|
+
"tenant_id": {"key": "properties.tenantId", "type": "str"},
|
|
120
|
+
"v1_legacy_mode": {"key": "properties.v1LegacyMode", "type": "bool"},
|
|
121
|
+
"workspace_hub_config": {"key": "properties.workspaceHubConfig", "type": "WorkspaceHubConfig"},
|
|
122
|
+
"workspace_id": {"key": "properties.workspaceId", "type": "str"},
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
# system_data: Optional[SystemData] = None,
|
|
129
|
+
# identity: Optional["ManagedServiceIdentity"] = None,
|
|
130
|
+
kind: Optional[str] = None,
|
|
131
|
+
location: Optional[str] = None,
|
|
132
|
+
# sku: Optional["Sku"] = None,
|
|
133
|
+
tags: Optional[Dict[str, str]] = None,
|
|
134
|
+
allow_public_access_when_behind_vnet: Optional[bool] = None,
|
|
135
|
+
allow_role_assignment_on_rg: Optional[bool] = None,
|
|
136
|
+
application_insights: Optional[str] = None,
|
|
137
|
+
associated_workspaces: Optional[List[str]] = None,
|
|
138
|
+
container_registries: Optional[List[str]] = None,
|
|
139
|
+
container_registry: Optional[str] = None,
|
|
140
|
+
description: Optional[str] = None,
|
|
141
|
+
discovery_url: Optional[str] = None,
|
|
142
|
+
enable_data_isolation: Optional[bool] = None,
|
|
143
|
+
enable_service_side_cmk_encryption: Optional[bool] = None,
|
|
144
|
+
enable_simplified_cmk: Optional[bool] = None,
|
|
145
|
+
enable_software_bill_of_materials: Optional[bool] = None,
|
|
146
|
+
# encryption: Optional["EncryptionProperty"] = None,
|
|
147
|
+
existing_workspaces: Optional[List[str]] = None,
|
|
148
|
+
# feature_store_settings: Optional["FeatureStoreSettings"] = None,
|
|
149
|
+
friendly_name: Optional[str] = None,
|
|
150
|
+
hbi_workspace: Optional[bool] = None,
|
|
151
|
+
hub_resource_id: Optional[str] = None,
|
|
152
|
+
image_build_compute: Optional[str] = None,
|
|
153
|
+
ip_allowlist: Optional[List[str]] = None,
|
|
154
|
+
key_vault: Optional[str] = None,
|
|
155
|
+
key_vaults: Optional[List[str]] = None,
|
|
156
|
+
# managed_network: Optional["ManagedNetworkSettings"] = None,
|
|
157
|
+
# network_acls: Optional["NetworkAcls"] = None,
|
|
158
|
+
primary_user_assigned_identity: Optional[str] = None,
|
|
159
|
+
provision_network_now: Optional[bool] = None,
|
|
160
|
+
# public_network_access: Optional[Union[str, "PublicNetworkAccessType"]] = None,
|
|
161
|
+
# serverless_compute_settings: Optional["ServerlessComputeSettings"] = None,
|
|
162
|
+
# service_managed_resources_settings: Optional["ServiceManagedResourcesSettings"] = None,
|
|
163
|
+
# shared_private_link_resources: Optional[List["SharedPrivateLinkResource"]] = None,
|
|
164
|
+
soft_delete_retention_in_days: Optional[int] = None,
|
|
165
|
+
storage_account: Optional[str] = None,
|
|
166
|
+
storage_accounts: Optional[List[str]] = None,
|
|
167
|
+
# system_datastores_auth_mode: Optional[Union[str, "SystemDatastoresAuthMode"]] = None,
|
|
168
|
+
v1_legacy_mode: Optional[bool] = None,
|
|
169
|
+
workspace_hub_config: Optional["WorkspaceHubConfig"] = None,
|
|
170
|
+
**kwargs
|
|
171
|
+
):
|
|
172
|
+
super(Workspace, self).__init__(**kwargs)
|
|
173
|
+
self.id: Optional[str] = None
|
|
174
|
+
self.name: Optional[str] = None
|
|
175
|
+
self.type: Optional[str] = None
|
|
176
|
+
# self.system_data = system_data
|
|
177
|
+
# self.identity = identity
|
|
178
|
+
self.kind = kind
|
|
179
|
+
self.location = location
|
|
180
|
+
# self.sku = sku
|
|
181
|
+
self.tags = tags
|
|
182
|
+
self.agents_endpoint_uri = None
|
|
183
|
+
self.allow_public_access_when_behind_vnet = allow_public_access_when_behind_vnet
|
|
184
|
+
self.allow_role_assignment_on_rg = allow_role_assignment_on_rg
|
|
185
|
+
self.application_insights = application_insights
|
|
186
|
+
self.associated_workspaces = associated_workspaces
|
|
187
|
+
self.container_registries = container_registries
|
|
188
|
+
self.container_registry = container_registry
|
|
189
|
+
self.description = description
|
|
190
|
+
self.discovery_url = discovery_url
|
|
191
|
+
self.enable_data_isolation = enable_data_isolation
|
|
192
|
+
self.enable_service_side_cmk_encryption = enable_service_side_cmk_encryption
|
|
193
|
+
self.enable_simplified_cmk = enable_simplified_cmk
|
|
194
|
+
self.enable_software_bill_of_materials = enable_software_bill_of_materials
|
|
195
|
+
# self.encryption = encryption
|
|
196
|
+
self.existing_workspaces = existing_workspaces
|
|
197
|
+
# self.feature_store_settings = feature_store_settings
|
|
198
|
+
self.friendly_name = friendly_name
|
|
199
|
+
self.hbi_workspace = hbi_workspace
|
|
200
|
+
self.hub_resource_id = hub_resource_id
|
|
201
|
+
self.image_build_compute = image_build_compute
|
|
202
|
+
self.ip_allowlist = ip_allowlist
|
|
203
|
+
self.key_vault = key_vault
|
|
204
|
+
self.key_vaults = key_vaults
|
|
205
|
+
# self.managed_network = managed_network
|
|
206
|
+
self.ml_flow_tracking_uri = None
|
|
207
|
+
# self.network_acls = network_acls
|
|
208
|
+
# self.notebook_info = None
|
|
209
|
+
self.primary_user_assigned_identity = primary_user_assigned_identity
|
|
210
|
+
self.private_endpoint_connections = None
|
|
211
|
+
self.private_link_count = None
|
|
212
|
+
self.provision_network_now = provision_network_now
|
|
213
|
+
self.provisioning_state = None
|
|
214
|
+
# self.public_network_access = public_network_access
|
|
215
|
+
# self.serverless_compute_settings = serverless_compute_settings
|
|
216
|
+
# self.service_managed_resources_settings = service_managed_resources_settings
|
|
217
|
+
self.service_provisioned_resource_group = None
|
|
218
|
+
# self.shared_private_link_resources = shared_private_link_resources
|
|
219
|
+
self.soft_delete_retention_in_days = soft_delete_retention_in_days
|
|
220
|
+
self.storage_account = storage_account
|
|
221
|
+
self.storage_accounts = storage_accounts
|
|
222
|
+
self.storage_hns_enabled = None
|
|
223
|
+
# self.system_datastores_auth_mode = system_datastores_auth_mode
|
|
224
|
+
self.tenant_id = None
|
|
225
|
+
self.v1_legacy_mode = v1_legacy_mode
|
|
226
|
+
self.workspace_hub_config = workspace_hub_config
|
|
227
|
+
self.workspace_id = None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# ---------------------------------------------------------
|
|
2
|
+
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
# ---------------------------------------------------------
|
|
4
|
+
import os
|
|
5
|
+
import logging
|
|
6
|
+
import time
|
|
7
|
+
import inspect
|
|
8
|
+
from typing import cast, Optional, Union
|
|
9
|
+
|
|
10
|
+
from azure.core.credentials import TokenCredential, AccessToken
|
|
11
|
+
from azure.identity import AzureCliCredential, DefaultAzureCredential, ManagedIdentityCredential
|
|
12
|
+
from azure.ai.evaluation._exceptions import ErrorBlame, ErrorCategory, ErrorTarget, EvaluationException
|
|
13
|
+
from ..simulator._model_tools._identity_manager import APITokenManager, AZURE_TOKEN_REFRESH_INTERVAL
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AzureMLTokenManager(APITokenManager):
|
|
17
|
+
"""API Token manager for Azure Management API.
|
|
18
|
+
|
|
19
|
+
:param token_scope: Token scopes for Azure endpoint
|
|
20
|
+
:type token_scope: str
|
|
21
|
+
:param logger: Logger object
|
|
22
|
+
:type logger: logging.Logger
|
|
23
|
+
:keyword kwargs: Additional keyword arguments
|
|
24
|
+
:paramtype kwargs: Dict
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
token_scope: str,
|
|
30
|
+
logger: logging.Logger,
|
|
31
|
+
credential: Optional[TokenCredential] = None,
|
|
32
|
+
):
|
|
33
|
+
super().__init__(logger, credential=credential)
|
|
34
|
+
self.token_scope = token_scope
|
|
35
|
+
self.token_expiry_time: Optional[int] = None
|
|
36
|
+
|
|
37
|
+
def get_aad_credential(self) -> Union[DefaultAzureCredential, ManagedIdentityCredential]:
|
|
38
|
+
"""Get the Azure credentials to use for the management APIs.
|
|
39
|
+
|
|
40
|
+
:return: Azure credentials
|
|
41
|
+
:rtype: DefaultAzureCredential or ManagedIdentityCredential
|
|
42
|
+
"""
|
|
43
|
+
# Adds some of the additional types credentials that the previous Azure AI ML code used
|
|
44
|
+
# These may or may not be needed but kept here for backwards compatibility
|
|
45
|
+
|
|
46
|
+
if os.getenv("AZUREML_OBO_ENABLED"):
|
|
47
|
+
# using Azure on behalf of credentials requires the use of the azure-ai-ml package
|
|
48
|
+
try:
|
|
49
|
+
from azure.ai.ml.identity import AzureMLOnBehalfOfCredential
|
|
50
|
+
|
|
51
|
+
self.logger.debug("User identity is configured, use OBO credential.")
|
|
52
|
+
return AzureMLOnBehalfOfCredential() # type: ignore
|
|
53
|
+
except (ModuleNotFoundError, ImportError):
|
|
54
|
+
raise EvaluationException( # pylint: disable=raise-missing-from
|
|
55
|
+
message=(
|
|
56
|
+
"The required packages for OBO credentials are missing.\n"
|
|
57
|
+
'To resolve this, please install them by running "pip install azure-ai-ml".'
|
|
58
|
+
),
|
|
59
|
+
target=ErrorTarget.EVALUATE,
|
|
60
|
+
category=ErrorCategory.MISSING_PACKAGE,
|
|
61
|
+
blame=ErrorBlame.USER_ERROR,
|
|
62
|
+
)
|
|
63
|
+
elif os.environ.get("PF_USE_AZURE_CLI_CREDENTIAL", "false").lower() == "true":
|
|
64
|
+
self.logger.debug("Use azure cli credential since specified in environment variable.")
|
|
65
|
+
return AzureCliCredential() # type: ignore
|
|
66
|
+
elif os.environ.get("IS_IN_CI_PIPELINE", "false").lower() == "true":
|
|
67
|
+
# use managed identity when executing in CI pipeline.
|
|
68
|
+
self.logger.debug("Use azure cli credential since in CI pipeline.")
|
|
69
|
+
return AzureCliCredential() # type: ignore
|
|
70
|
+
else:
|
|
71
|
+
# Fall back to using the parent implementation
|
|
72
|
+
return super().get_aad_credential()
|
|
73
|
+
|
|
74
|
+
def get_token(self) -> str:
|
|
75
|
+
"""Get the API token. If the token is not available or has expired, refresh the token.
|
|
76
|
+
|
|
77
|
+
:return: API token
|
|
78
|
+
:rtype: str
|
|
79
|
+
"""
|
|
80
|
+
if self._token_needs_update():
|
|
81
|
+
credential = cast(TokenCredential, self.credential)
|
|
82
|
+
access_token = credential.get_token(self.token_scope)
|
|
83
|
+
self._update_token(access_token)
|
|
84
|
+
|
|
85
|
+
return cast(str, self.token) # check for none is hidden in the _token_needs_update method
|
|
86
|
+
|
|
87
|
+
async def get_token_async(self) -> str:
|
|
88
|
+
"""Get the API token asynchronously. If the token is not available or has expired, refresh it.
|
|
89
|
+
|
|
90
|
+
:return: API token
|
|
91
|
+
:rtype: str
|
|
92
|
+
"""
|
|
93
|
+
if self._token_needs_update():
|
|
94
|
+
credential = cast(TokenCredential, self.credential)
|
|
95
|
+
get_token_method = credential.get_token(self.token_scope)
|
|
96
|
+
if inspect.isawaitable(get_token_method):
|
|
97
|
+
access_token = await get_token_method
|
|
98
|
+
else:
|
|
99
|
+
access_token = get_token_method
|
|
100
|
+
self._update_token(access_token)
|
|
101
|
+
|
|
102
|
+
return cast(str, self.token) # check for none is hidden in the _token_needs_update method
|
|
103
|
+
|
|
104
|
+
def _token_needs_update(self) -> bool:
|
|
105
|
+
current_time = time.time()
|
|
106
|
+
return (
|
|
107
|
+
self.token is None
|
|
108
|
+
or self.last_refresh_time is None
|
|
109
|
+
or self.token_expiry_time is None
|
|
110
|
+
or self.token_expiry_time - current_time < AZURE_TOKEN_REFRESH_INTERVAL
|
|
111
|
+
or current_time - self.last_refresh_time > AZURE_TOKEN_REFRESH_INTERVAL
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _update_token(self, access_token: AccessToken) -> None:
|
|
115
|
+
self.token = cast(str, access_token.token)
|
|
116
|
+
self.token_expiry_time = access_token.expires_on
|
|
117
|
+
self.last_refresh_time = time.time()
|
|
118
|
+
self.logger.info("Refreshed Azure management token.")
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
# ---------------------------------------------------------
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import functools
|
|
6
7
|
import inspect
|
|
7
8
|
import logging
|
|
@@ -149,6 +150,9 @@ def _get_indentation_size(doc_string: str) -> int:
|
|
|
149
150
|
def _should_skip_warning():
|
|
150
151
|
skip_warning_msg = False
|
|
151
152
|
|
|
153
|
+
if os.getenv("AI_EVALS_DISABLE_EXPERIMENTAL_WARNING", "false").lower() == "true":
|
|
154
|
+
skip_warning_msg = True
|
|
155
|
+
|
|
152
156
|
# Cases where we want to suppress the warning:
|
|
153
157
|
# 1. When converting from REST object to SDK object
|
|
154
158
|
for frame in inspect.stack():
|
|
@@ -3,20 +3,44 @@
|
|
|
3
3
|
# ---------------------------------------------------------
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
-
from typing import List
|
|
6
|
+
from typing import List, Callable, Any
|
|
7
7
|
|
|
8
8
|
from azure.ai.evaluation._exceptions import EvaluationException, ErrorBlame, ErrorCategory, ErrorTarget
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def list_sum(lst: List[float]) -> float:
|
|
12
|
+
"""Given a list of floats, return the sum of the values.
|
|
13
|
+
|
|
14
|
+
:param lst: A list of floats.
|
|
15
|
+
:type lst: List[float]
|
|
16
|
+
:return: The sum of the values in the list.
|
|
17
|
+
:rtype: float
|
|
18
|
+
"""
|
|
19
|
+
|
|
12
20
|
return sum(lst)
|
|
13
21
|
|
|
14
22
|
|
|
15
23
|
def list_mean(lst: List[float]) -> float:
|
|
24
|
+
"""Given a list of floats, calculate the mean of the values.
|
|
25
|
+
|
|
26
|
+
:param lst: A list of floats.
|
|
27
|
+
:type lst: List[float]
|
|
28
|
+
:return: The mean of the values in the list.
|
|
29
|
+
:rtype: float
|
|
30
|
+
"""
|
|
31
|
+
|
|
16
32
|
return list_sum(lst) / len(lst)
|
|
17
33
|
|
|
18
34
|
|
|
19
35
|
def list_mean_nan_safe(lst: List[float]) -> float:
|
|
36
|
+
"""Given a list of floats, remove all nan or None values, then calculate the mean of the remaining values.
|
|
37
|
+
|
|
38
|
+
:param lst: A list of floats.
|
|
39
|
+
:type lst: List[float]
|
|
40
|
+
:return: The mean of the values in the list.
|
|
41
|
+
:rtype: float
|
|
42
|
+
"""
|
|
43
|
+
|
|
20
44
|
msg = "All score values are NaN. The mean cannot be calculated."
|
|
21
45
|
if all(math.isnan(l) for l in lst):
|
|
22
46
|
raise EvaluationException(
|
|
@@ -26,4 +50,40 @@ def list_mean_nan_safe(lst: List[float]) -> float:
|
|
|
26
50
|
category=ErrorCategory.INVALID_VALUE,
|
|
27
51
|
target=ErrorTarget.CONVERSATION,
|
|
28
52
|
)
|
|
29
|
-
return list_mean([l for l in lst if not
|
|
53
|
+
return list_mean([l for l in lst if not is_none_or_nan(l)])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def apply_transform_nan_safe(lst: List[float], transform_fn: Callable[[float], Any]) -> List[Any]:
|
|
57
|
+
"""Given a list of floats, remove all nan values, then apply the inputted transform function
|
|
58
|
+
to the remaining values, and return the resulting list of outputted values.
|
|
59
|
+
|
|
60
|
+
:param lst: A list of floats.
|
|
61
|
+
:type lst: List[float]
|
|
62
|
+
:param transform_fn: A function that produces something when applied to a float.
|
|
63
|
+
:type transform_fn: Callable[[float], Any]
|
|
64
|
+
:return: A list of the transformed values.
|
|
65
|
+
:rtype: List[Any]
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
msg = "All score values are NaN. The mean cannot be calculated."
|
|
69
|
+
if all(math.isnan(l) for l in lst):
|
|
70
|
+
raise EvaluationException(
|
|
71
|
+
message=msg,
|
|
72
|
+
internal_message=msg,
|
|
73
|
+
blame=ErrorBlame.USER_ERROR,
|
|
74
|
+
category=ErrorCategory.INVALID_VALUE,
|
|
75
|
+
target=ErrorTarget.CONVERSATION,
|
|
76
|
+
)
|
|
77
|
+
return [transform_fn(l) for l in lst if not is_none_or_nan(l)]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def is_none_or_nan(val: float) -> bool:
|
|
81
|
+
"""math.isnan raises an error if None is inputted. This is a more robust wrapper.
|
|
82
|
+
|
|
83
|
+
:param val: The value to check.
|
|
84
|
+
:type val: float
|
|
85
|
+
:return: Whether the value is None or NaN.
|
|
86
|
+
:rtype: bool
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
return val is None or math.isnan(val)
|