azure-ai-evaluation 1.0.1__py3-none-any.whl → 1.2.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 +204 -0
- azure/ai/evaluation/_azure/_models.py +227 -0
- azure/ai/evaluation/_azure/_token_manager.py +118 -0
- azure/ai/evaluation/_common/rai_service.py +30 -21
- azure/ai/evaluation/_constants.py +19 -0
- azure/ai/evaluation/_evaluate/_batch_run/__init__.py +2 -1
- azure/ai/evaluation/_evaluate/_batch_run/target_run_context.py +1 -1
- azure/ai/evaluation/_evaluate/_eval_run.py +16 -43
- azure/ai/evaluation/_evaluate/_evaluate.py +76 -44
- azure/ai/evaluation/_evaluate/_utils.py +93 -34
- azure/ai/evaluation/_evaluators/_bleu/_bleu.py +46 -25
- azure/ai/evaluation/_evaluators/_common/__init__.py +2 -0
- azure/ai/evaluation/_evaluators/_common/_base_eval.py +140 -5
- azure/ai/evaluation/_evaluators/_common/_base_multi_eval.py +61 -0
- azure/ai/evaluation/_evaluators/_common/_base_prompty_eval.py +12 -1
- azure/ai/evaluation/_evaluators/_common/_base_rai_svc_eval.py +40 -2
- azure/ai/evaluation/_evaluators/_common/_conversation_aggregators.py +49 -0
- azure/ai/evaluation/_evaluators/_content_safety/_content_safety.py +6 -43
- azure/ai/evaluation/_evaluators/_content_safety/_hate_unfairness.py +2 -0
- azure/ai/evaluation/_evaluators/_content_safety/_self_harm.py +2 -0
- azure/ai/evaluation/_evaluators/_content_safety/_sexual.py +2 -0
- azure/ai/evaluation/_evaluators/_content_safety/_violence.py +2 -0
- azure/ai/evaluation/_evaluators/_f1_score/_f1_score.py +61 -68
- azure/ai/evaluation/_evaluators/_gleu/_gleu.py +45 -23
- azure/ai/evaluation/_evaluators/_meteor/_meteor.py +55 -34
- azure/ai/evaluation/_evaluators/_qa/_qa.py +32 -27
- azure/ai/evaluation/_evaluators/_rouge/_rouge.py +44 -23
- azure/ai/evaluation/_evaluators/_similarity/_similarity.py +42 -82
- azure/ai/evaluation/_http_utils.py +6 -4
- 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 +2 -0
- azure/ai/evaluation/simulator/_adversarial_simulator.py +35 -16
- azure/ai/evaluation/simulator/_conversation/__init__.py +128 -7
- azure/ai/evaluation/simulator/_conversation/_conversation.py +0 -1
- azure/ai/evaluation/simulator/_indirect_attack_simulator.py +1 -0
- azure/ai/evaluation/simulator/_model_tools/_rai_client.py +40 -0
- azure/ai/evaluation/simulator/_model_tools/_template_handler.py +1 -0
- azure/ai/evaluation/simulator/_simulator.py +24 -13
- {azure_ai_evaluation-1.0.1.dist-info → azure_ai_evaluation-1.2.0.dist-info}/METADATA +84 -15
- {azure_ai_evaluation-1.0.1.dist-info → azure_ai_evaluation-1.2.0.dist-info}/RECORD +47 -41
- {azure_ai_evaluation-1.0.1.dist-info → azure_ai_evaluation-1.2.0.dist-info}/NOTICE.txt +0 -0
- {azure_ai_evaluation-1.0.1.dist-info → azure_ai_evaluation-1.2.0.dist-info}/WHEEL +0 -0
- {azure_ai_evaluation-1.0.1.dist-info → azure_ai_evaluation-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,204 @@
|
|
|
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-07-01-preview"
|
|
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(
|
|
73
|
+
self, workspace_name: str, *, include_credentials: bool = False, **kwargs: Any
|
|
74
|
+
) -> BlobStoreInfo:
|
|
75
|
+
# 1. Get the default blob store
|
|
76
|
+
# REST API documentation:
|
|
77
|
+
# https://learn.microsoft.com/rest/api/azureml/datastores/list?view=rest-azureml-2024-10-01
|
|
78
|
+
url = self._generate_path( # pylint: disable=specify-parameter-names-in-call
|
|
79
|
+
*PATH_ML_WORKSPACES, workspace_name, "datastores"
|
|
80
|
+
)
|
|
81
|
+
headers = self._get_headers()
|
|
82
|
+
|
|
83
|
+
stores_response = self._http_client.request(
|
|
84
|
+
method="GET",
|
|
85
|
+
url=url,
|
|
86
|
+
params={QUERY_KEY_API_VERSION: self._api_version, "isDefault": True, "count": 1, "orderByAsc": "false"},
|
|
87
|
+
headers=headers,
|
|
88
|
+
)
|
|
89
|
+
self._throw_on_http_error(stores_response, "list default workspace datastore")
|
|
90
|
+
|
|
91
|
+
json = stores_response.json()["value"][0]
|
|
92
|
+
props_json = json["properties"]
|
|
93
|
+
name = json["name"]
|
|
94
|
+
account_name = props_json["accountName"]
|
|
95
|
+
endpoint = props_json["endpoint"]
|
|
96
|
+
container_name = props_json["containerName"]
|
|
97
|
+
credential_type = props_json.get("credentials", {}).get("credentialsType")
|
|
98
|
+
|
|
99
|
+
# 2. Get the SAS token to use for accessing the blob store
|
|
100
|
+
# REST API documentation:
|
|
101
|
+
# https://learn.microsoft.com/rest/api/azureml/datastores/list-secrets?view=rest-azureml-2024-10-01
|
|
102
|
+
blob_store_credential: Optional[Union[AzureSasCredential, TokenCredential, str]]
|
|
103
|
+
if not include_credentials:
|
|
104
|
+
blob_store_credential = None
|
|
105
|
+
elif credential_type and credential_type.lower() == "none":
|
|
106
|
+
# If storage account key access is disabled, and only Microsoft Entra ID authentication is available,
|
|
107
|
+
# the credentialsType will be "None" and we should not attempt to get the secrets.
|
|
108
|
+
blob_store_credential = self.get_credential()
|
|
109
|
+
else:
|
|
110
|
+
url = self._generate_path(
|
|
111
|
+
*PATH_ML_WORKSPACES, workspace_name, "datastores", "workspaceblobstore", "listSecrets"
|
|
112
|
+
)
|
|
113
|
+
secrets_response = self._http_client.request(
|
|
114
|
+
method="POST",
|
|
115
|
+
url=url,
|
|
116
|
+
json={
|
|
117
|
+
"expirableSecret": True,
|
|
118
|
+
"expireAfterHours": int(kwargs.get("key_expiration_hours", 1)),
|
|
119
|
+
},
|
|
120
|
+
params={
|
|
121
|
+
QUERY_KEY_API_VERSION: self._api_version,
|
|
122
|
+
},
|
|
123
|
+
headers=headers,
|
|
124
|
+
)
|
|
125
|
+
self._throw_on_http_error(secrets_response, "workspace datastore secrets")
|
|
126
|
+
|
|
127
|
+
secrets_json = secrets_response.json()
|
|
128
|
+
secrets_type = secrets_json["secretsType"].lower()
|
|
129
|
+
|
|
130
|
+
# As per this website, only SAS tokens, access tokens, or Entra IDs are valid for accessing blob data
|
|
131
|
+
# stores:
|
|
132
|
+
# https://learn.microsoft.com/rest/api/storageservices/authorize-requests-to-azure-storage.
|
|
133
|
+
if secrets_type == "sas":
|
|
134
|
+
blob_store_credential = AzureSasCredential(secrets_json["sasToken"])
|
|
135
|
+
elif secrets_type == "accountkey":
|
|
136
|
+
# To support older versions of azure-storage-blob better, we return a string here instead of
|
|
137
|
+
# an AzureNamedKeyCredential
|
|
138
|
+
blob_store_credential = secrets_json["key"]
|
|
139
|
+
else:
|
|
140
|
+
raise EvaluationException(
|
|
141
|
+
message=f"The '{account_name}' blob store does not use a recognized credential type.",
|
|
142
|
+
internal_message=f"The credential type is '{secrets_type}'",
|
|
143
|
+
target=ErrorTarget.EVALUATE,
|
|
144
|
+
category=ErrorCategory.INVALID_VALUE,
|
|
145
|
+
blame=ErrorBlame.SYSTEM_ERROR,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return BlobStoreInfo(name, account_name, endpoint, container_name, blob_store_credential)
|
|
149
|
+
|
|
150
|
+
def workspace_get_info(self, workspace_name: str) -> Workspace:
|
|
151
|
+
# https://learn.microsoft.com/rest/api/azureml/workspaces/get?view=rest-azureml-2024-10-01
|
|
152
|
+
workspace_response = self._http_client.request(
|
|
153
|
+
"GET",
|
|
154
|
+
self._generate_path(*PATH_ML_WORKSPACES, workspace_name),
|
|
155
|
+
params={QUERY_KEY_API_VERSION: self._api_version},
|
|
156
|
+
headers=self._get_headers(),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
self._throw_on_http_error(workspace_response, f"get '{workspace_name}' workspace")
|
|
160
|
+
workspace = Workspace.deserialize(workspace_response)
|
|
161
|
+
return workspace
|
|
162
|
+
|
|
163
|
+
def _get_token_manager(self) -> AzureMLTokenManager:
|
|
164
|
+
# Lazy init since getting credentials in the constructor can take a long time in some situations
|
|
165
|
+
if self._token_manager is None:
|
|
166
|
+
with self._lock:
|
|
167
|
+
if self._token_manager is None:
|
|
168
|
+
self._token_manager = AzureMLTokenManager(
|
|
169
|
+
TokenScope.DEFAULT_AZURE_MANAGEMENT.value, self._logger, credential=self._credential
|
|
170
|
+
)
|
|
171
|
+
self._credential = self._token_manager.credential
|
|
172
|
+
|
|
173
|
+
return self._token_manager
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def _throw_on_http_error(response: HttpResponse, description: str, valid_status: Optional[Set[int]] = None) -> None:
|
|
177
|
+
if valid_status and (response.status_code in valid_status):
|
|
178
|
+
return
|
|
179
|
+
if response.status_code >= 200 and response.status_code < 300:
|
|
180
|
+
# nothing to see here, move along
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
message = f"The {description} request failed with HTTP {response.status_code}"
|
|
184
|
+
try:
|
|
185
|
+
error_json = response.json()["error"]
|
|
186
|
+
additional_info = f"({error_json['code']}) {error_json['message']}"
|
|
187
|
+
message += f" - {additional_info}"
|
|
188
|
+
except (JSONDecodeError, ValueError, KeyError):
|
|
189
|
+
pass
|
|
190
|
+
|
|
191
|
+
raise EvaluationException(
|
|
192
|
+
message=message,
|
|
193
|
+
target=ErrorTarget.EVALUATE,
|
|
194
|
+
category=ErrorCategory.FAILED_EXECUTION,
|
|
195
|
+
blame=ErrorBlame.SYSTEM_ERROR,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
def _generate_path(self, *paths: str) -> str:
|
|
199
|
+
sanitized_paths = [quote(path, safe="") for path in paths]
|
|
200
|
+
url = self._base_url + "/" + str.join("/", sanitized_paths)
|
|
201
|
+
return url
|
|
202
|
+
|
|
203
|
+
def _get_headers(self) -> Dict[str, str]:
|
|
204
|
+
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, TokenCredential
|
|
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, TokenCredential, 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.")
|
|
@@ -72,18 +72,21 @@ def get_formatted_template(data: dict, annotation_task: str) -> str:
|
|
|
72
72
|
return user_text.replace("'", '\\"')
|
|
73
73
|
|
|
74
74
|
|
|
75
|
-
def get_common_headers(token: str) -> Dict:
|
|
75
|
+
def get_common_headers(token: str, evaluator_name: Optional[str] = None) -> Dict:
|
|
76
76
|
"""Get common headers for the HTTP request
|
|
77
77
|
|
|
78
78
|
:param token: The Azure authentication token.
|
|
79
79
|
:type token: str
|
|
80
|
+
:param evaluator_name: The evaluator name. Default is None.
|
|
81
|
+
:type evaluator_name: str
|
|
80
82
|
:return: The common headers.
|
|
81
83
|
:rtype: Dict
|
|
82
84
|
"""
|
|
85
|
+
user_agent = f"{USER_AGENT} (type=evaluator; subtype={evaluator_name})" if evaluator_name else USER_AGENT
|
|
83
86
|
return {
|
|
84
87
|
"Authorization": f"Bearer {token}",
|
|
85
88
|
"Content-Type": "application/json",
|
|
86
|
-
"User-Agent":
|
|
89
|
+
"User-Agent": user_agent,
|
|
87
90
|
# Handle "RuntimeError: Event loop is closed" from httpx AsyncClient
|
|
88
91
|
# https://github.com/encode/httpx/discussions/2959
|
|
89
92
|
"Connection": "close",
|
|
@@ -175,7 +178,9 @@ def generate_payload(normalized_user_text: str, metric: str, annotation_task: st
|
|
|
175
178
|
)
|
|
176
179
|
|
|
177
180
|
|
|
178
|
-
async def submit_request(
|
|
181
|
+
async def submit_request(
|
|
182
|
+
data: dict, metric: str, rai_svc_url: str, token: str, annotation_task: str, evaluator_name: str
|
|
183
|
+
) -> str:
|
|
179
184
|
"""Submit request to Responsible AI service for evaluation and return operation ID
|
|
180
185
|
|
|
181
186
|
:param data: The data to evaluate.
|
|
@@ -188,6 +193,8 @@ async def submit_request(data: dict, metric: str, rai_svc_url: str, token: str,
|
|
|
188
193
|
:type token: str
|
|
189
194
|
:param annotation_task: The annotation task to use.
|
|
190
195
|
:type annotation_task: str
|
|
196
|
+
:param evaluator_name: The evaluator name.
|
|
197
|
+
:type evaluator_name: str
|
|
191
198
|
:return: The operation ID.
|
|
192
199
|
:rtype: str
|
|
193
200
|
"""
|
|
@@ -195,7 +202,7 @@ async def submit_request(data: dict, metric: str, rai_svc_url: str, token: str,
|
|
|
195
202
|
payload = generate_payload(normalized_user_text, metric, annotation_task=annotation_task)
|
|
196
203
|
|
|
197
204
|
url = rai_svc_url + "/submitannotation"
|
|
198
|
-
headers = get_common_headers(token)
|
|
205
|
+
headers = get_common_headers(token, evaluator_name)
|
|
199
206
|
|
|
200
207
|
async with get_async_http_client_with_timeout() as client:
|
|
201
208
|
http_response = await client.post(url, json=payload, headers=headers)
|
|
@@ -493,24 +500,26 @@ async def evaluate_with_rai_service(
|
|
|
493
500
|
credential: TokenCredential,
|
|
494
501
|
annotation_task: str = Tasks.CONTENT_HARM,
|
|
495
502
|
metric_display_name=None,
|
|
503
|
+
evaluator_name=None,
|
|
496
504
|
) -> Dict[str, Union[str, float]]:
|
|
497
|
-
"""
|
|
505
|
+
"""Evaluate the content safety of the response using Responsible AI service
|
|
498
506
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
507
|
+
:param data: The data to evaluate.
|
|
508
|
+
:type data: dict
|
|
509
|
+
:param metric_name: The evaluation metric to use.
|
|
510
|
+
:type metric_name: str
|
|
511
|
+
:param project_scope: The Azure AI project scope details.
|
|
512
|
+
:type project_scope: Dict
|
|
513
|
+
:param credential: The Azure authentication credential.
|
|
514
|
+
:type credential: ~azure.core.credentials.TokenCredential
|
|
515
|
+
:param annotation_task: The annotation task to use.
|
|
516
|
+
:type annotation_task: str
|
|
517
|
+
:param metric_display_name: The display name of metric to use.
|
|
518
|
+
:type metric_display_name: str
|
|
519
|
+
:param evaluator_name: The evaluator name to use.
|
|
520
|
+
:type evaluator_name: str
|
|
521
|
+
:return: The parsed annotation result.
|
|
522
|
+
:rtype: Dict[str, Union[str, float]]
|
|
514
523
|
"""
|
|
515
524
|
|
|
516
525
|
# Get RAI service URL from discovery service and check service availability
|
|
@@ -519,7 +528,7 @@ async def evaluate_with_rai_service(
|
|
|
519
528
|
await ensure_service_availability(rai_svc_url, token, annotation_task)
|
|
520
529
|
|
|
521
530
|
# Submit annotation request and fetch result
|
|
522
|
-
operation_id = await submit_request(data, metric_name, rai_svc_url, token, annotation_task)
|
|
531
|
+
operation_id = await submit_request(data, metric_name, rai_svc_url, token, annotation_task, evaluator_name)
|
|
523
532
|
annotation_response = cast(List[Dict], await fetch_result(operation_id, rai_svc_url, credential, token))
|
|
524
533
|
result = parse_response(annotation_response, metric_name, metric_display_name)
|
|
525
534
|
|