Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Users/singankit/evaluator crud #35001

Merged
merged 37 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
60cadc9
Adding tests to capture groundedness with expected values
singankit Feb 15, 2024
d122f3d
Merge branch 'Azure:main' into main
singankit Feb 15, 2024
08b5232
Merge branch 'Azure:main' into main
singankit Feb 27, 2024
cd21d6a
Merge branch 'Azure:main' into main
singankit Feb 29, 2024
ba9d3f4
Merge branch 'Azure:main' into main
singankit Mar 4, 2024
0b5357d
Merge branch 'Azure:main' into main
singankit Mar 4, 2024
cd7bcbf
Merge branch 'Azure:main' into main
singankit Mar 15, 2024
efc5db3
Merge branch 'Azure:main' into main
singankit Mar 22, 2024
8c18546
Merge branch 'Azure:main' into main
singankit Mar 22, 2024
01cec57
Merge branch 'Azure:main' into main
singankit Mar 26, 2024
d1cb943
Merge branch 'Azure:main' into main
singankit Mar 28, 2024
c719ea0
Evalutors API
singankit Mar 28, 2024
13e385b
Merge branch 'main' of https://github.com/singankit/azure-sdk-for-python
singankit Mar 28, 2024
33baee9
Merge branch 'Azure:main' into main
singankit Mar 28, 2024
c2a9656
Add tests and minor fixes
nick863 Apr 5, 2024
1768f21
Add CHANGELOG
nick863 Apr 5, 2024
75521d9
Fix linters
nick863 Apr 6, 2024
975bfa4
Fix mypy
nick863 Apr 6, 2024
c819caf
Reuse model operations code for evaluator operations.
nick863 Apr 8, 2024
f9687bd
Do not allow creating the different type of a model, if the previous …
nick863 Apr 9, 2024
8771e31
Do not allow creation of evaluators by ModelOperations
nick863 Apr 10, 2024
d2d4caf
Merge pull request #2 from nick863/nirovins/add_utit_test_and_fix
singankit Apr 11, 2024
a364dfd
Merge branch 'Azure:main' into main
singankit Apr 23, 2024
78a110c
Fixes
nick863 May 2, 2024
2c2b50f
Merge branch 'main' of https://github.com/singankit/azure-sdk-for-pyt…
nick863 May 3, 2024
9a2167a
Merge to main
nick863 May 3, 2024
0987af4
Merge pull request #4 from singankit/nirovins/merge_to_azure_main
nick863 May 3, 2024
578b3b8
Merge to main
nick863 May 3, 2024
1a0953c
Linter fix
nick863 May 3, 2024
48e98ab
Merge branch 'main' of https://github.com/Azure/azure-sdk-for-python …
nick863 May 3, 2024
030c5c9
Merge pull request #5 from singankit/nirovins/merge_fork
nick863 May 3, 2024
c98d777
Make new recordings
nick863 May 3, 2024
8423413
Fix
nick863 May 3, 2024
7cf23c8
Fix
nick863 May 3, 2024
017e512
Fix
nick863 May 4, 2024
e74f19d
Fix typo
nick863 May 4, 2024
bd2a5de
Fix linters and unittests
nick863 May 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sdk/ml/azure-ai-ml/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.16.0 (unreleased)

### Features Added
- Add experimental support for working with Promptflow evaluators: `ml_client.evaluators`.
- Many changes to the Connection entity class and its associated operations.
- Workspace Connection `list`, `get`, and `create_or_update` operations now include an optional `populate_secrets` input, which causes the operations to try making a secondary call to fill in the returned connections' credential info if possible. Only works with api key-based credentials for now.
- Many workspace connection subtypes added. The full list of subclasses is now:
Expand Down
2 changes: 1 addition & 1 deletion sdk/ml/azure-ai-ml/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "python",
"TagPrefix": "python/ml/azure-ai-ml",
"Tag": "python/ml/azure-ai-ml_8c61dc0136"
"Tag": "python/ml/azure-ai-ml_ce8aa03671"
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
FeatureSetOperations,
IndexOperations,
ModelOperations,
EvaluatorOperations,
)
from azure.ai.ml.operations._code_operations import CodeOperations

Expand Down Expand Up @@ -463,7 +464,12 @@ def _update_gen2_metadata(name, version, indicator_file, storage_client) -> None
def _check_and_upload_path(
artifact: T,
asset_operations: Union[
"DataOperations", "ModelOperations", "CodeOperations", "FeatureSetOperations", "IndexOperations"
"DataOperations",
"ModelOperations",
"EvaluatorOperations",
"CodeOperations",
"FeatureSetOperations",
"IndexOperations",
],
artifact_type: str,
datastore_name: Optional[str] = None,
Expand Down
30 changes: 30 additions & 0 deletions sdk/ml/azure-ai-ml/azure/ai/ml/_ml_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
JobOperations,
MarketplaceSubscriptionOperations,
ModelOperations,
EvaluatorOperations,
OnlineDeploymentOperations,
OnlineEndpointOperations,
RegistryOperations,
Expand Down Expand Up @@ -537,6 +538,25 @@ def __init__(
registry_reference=registry_reference,
**app_insights_handler_kwargs, # type: ignore[arg-type]
)
# Evaluators
self._evaluators = EvaluatorOperations(
nick863 marked this conversation as resolved.
Show resolved Hide resolved
self._operation_scope,
self._operation_config,
(
self._service_client_10_2021_dataplanepreview
if registry_name or registry_reference
else self._service_client_08_2023_preview
),
self._datastores,
self._operation_container,
requests_pipeline=self._requests_pipeline,
control_plane_client=self._service_client_08_2023_preview,
workspace_rg=self._ws_rg,
workspace_sub=self._ws_sub,
registry_reference=registry_reference,
**app_insights_handler_kwargs, # type: ignore[arg-type]
)

self._operation_container.add(AzureMLResourceType.MODEL, self._models)
self._code = CodeOperations(
self._ws_operation_scope if registry_reference else self._operation_scope,
Expand Down Expand Up @@ -948,6 +968,16 @@ def models(self) -> ModelOperations:
"""
return self._models

@property
nick863 marked this conversation as resolved.
Show resolved Hide resolved
@experimental
def evaluators(self) -> EvaluatorOperations:
"""A collection of model related operations.

:return: Model operations
:rtype: ~azure.ai.ml.operations.ModelOperations
"""
return self._evaluators

@property
def online_endpoints(self) -> OnlineEndpointOperations:
"""A collection of online endpoint related operations.
Expand Down
8 changes: 8 additions & 0 deletions sdk/ml/azure-ai-ml/azure/ai/ml/_utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1419,3 +1419,11 @@ def extract_name_and_version(azureml_id: str) -> Dict[str, str]:
"name": name,
"version": version,
}


def _get_evaluator_properties():
return {"is-promptflow": "true", "is-evaluator": "true"}


def _is_evaluator(properties: Dict[str, str]) -> bool:
return properties.get("is-evaluator") == "true" and properties.get("is-promptflow") == "true"
2 changes: 2 additions & 0 deletions sdk/ml/azure-ai-ml/azure/ai/ml/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from ._connections_operations import ConnectionsOperations
from ._workspace_operations import WorkspaceOperations
from ._workspace_outbound_rule_operations import WorkspaceOutboundRuleOperations
from ._evaluator_operations import EvaluatorOperations
from ._serverless_endpoint_operations import ServerlessEndpointOperations
from ._marketplace_subscription_operations import MarketplaceSubscriptionOperations

Expand All @@ -38,6 +39,7 @@
"DatastoreOperations",
"JobOperations",
"ModelOperations",
"EvaluatorOperations",
"WorkspaceOperations",
"RegistryOperations",
"OnlineEndpointOperations",
Expand Down
231 changes: 231 additions & 0 deletions sdk/ml/azure-ai-ml/azure/ai/ml/operations/_evaluator_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------

# pylint: disable=protected-access,no-value-for-parameter,disable=docstring-missing-return,docstring-missing-param,docstring-missing-rtype,ungrouped-imports,line-too-long,too-many-statements

from os import PathLike
from typing import Any, Dict, Iterable, Optional, Union, cast
from azure.ai.ml._restclient.v2021_10_01_dataplanepreview import (
AzureMachineLearningWorkspaces as ServiceClient102021Dataplane,
)
from azure.ai.ml._restclient.v2023_08_01_preview import (
AzureMachineLearningWorkspaces as ServiceClient082023Preview,
)
from azure.ai.ml._restclient.v2023_08_01_preview.models import (
ListViewType,
)
from azure.ai.ml._scope_dependent_operations import (
OperationConfig,
OperationsContainer,
OperationScope,
_ScopeDependentOperations,
)
from azure.ai.ml._telemetry import ActivityType, monitor_with_activity
from azure.ai.ml._utils._experimental import experimental
from azure.ai.ml._utils._logger_utils import OpsLogger
from azure.ai.ml._utils.utils import (
_get_evaluator_properties,
_is_evaluator,
)
from azure.ai.ml.entities._assets import Environment, Model, ModelPackage
from azure.ai.ml.entities._assets.workspace_asset_reference import (
WorkspaceAssetReference,
)
from azure.ai.ml.exceptions import (
UnsupportedOperationError,
)
from azure.ai.ml.operations._datastore_operations import DatastoreOperations
from azure.core.exceptions import ResourceNotFoundError

from azure.ai.ml.operations._model_operations import ModelOperations

ops_logger = OpsLogger(__name__)
module_logger = ops_logger.module_logger


class EvaluatorOperations(_ScopeDependentOperations):
nick863 marked this conversation as resolved.
Show resolved Hide resolved
"""EvaluatorOperations.

You should not instantiate this class directly. Instead, you should create an MLClient instance that instantiates it
for you and attaches it as an attribute.

:param operation_scope: Scope variables for the operations classes of an MLClient object.
:type operation_scope: ~azure.ai.ml._scope_dependent_operations.OperationScope
:param operation_config: Common configuration for operations classes of an MLClient object.
:type operation_config: ~azure.ai.ml._scope_dependent_operations.OperationConfig
:param service_client: Service client to allow end users to operate on Azure Machine Learning Workspace
resources (ServiceClient082023Preview or ServiceClient102021Dataplane).
:type service_client: typing.Union[
azure.ai.ml._restclient.v2023_04_01_preview._azure_machine_learning_workspaces.AzureMachineLearningWorkspaces,
azure.ai.ml._restclient.v2021_10_01_dataplanepreview._azure_machine_learning_workspaces.
AzureMachineLearningWorkspaces]
:param datastore_operations: Represents a client for performing operations on Datastores.
:type datastore_operations: ~azure.ai.ml.operations._datastore_operations.DatastoreOperations
:param all_operations: All operations classes of an MLClient object.
:type all_operations: ~azure.ai.ml._scope_dependent_operations.OperationsContainer
"""

# pylint: disable=unused-argument
def __init__(
self,
operation_scope: OperationScope,
operation_config: OperationConfig,
service_client: Union[ServiceClient082023Preview, ServiceClient102021Dataplane],
datastore_operations: DatastoreOperations,
all_operations: Optional[OperationsContainer] = None,
**kwargs,
):
super(EvaluatorOperations, self).__init__(operation_scope, operation_config)

ops_logger.update_info(kwargs)
self._model_op = ModelOperations(
operation_scope=operation_scope,
operation_config=operation_config,
service_client=service_client,
datastore_operations=datastore_operations,
all_operations=all_operations,
**{ModelOperations._IS_EVALUATOR: True},
**kwargs,
)
self._operation_scope = self._model_op._operation_scope
self._datastore_operation = self._model_op._datastore_operation

@monitor_with_activity(ops_logger, "Evaluator.CreateOrUpdate", ActivityType.PUBLICAPI)
def create_or_update( # type: ignore
nick863 marked this conversation as resolved.
Show resolved Hide resolved
nick863 marked this conversation as resolved.
Show resolved Hide resolved
self, model: Union[Model, WorkspaceAssetReference]
) -> Model: # TODO: Are we going to implement job_name?
"""Returns created or updated model asset.

:param model: Model asset object.
:type model: ~azure.ai.ml.entities.Model
:raises ~azure.ai.ml.exceptions.AssetPathException: Raised when the Model artifact path is
already linked to another asset
:raises ~azure.ai.ml.exceptions.ValidationException: Raised if Model cannot be successfully validated.
Details will be provided in the error message.
:raises ~azure.ai.ml.exceptions.EmptyDirectoryError: Raised if local path provided points to an empty directory.
:return: Model asset object.
:rtype: ~azure.ai.ml.entities.Model
"""
model.properties.update(_get_evaluator_properties())
return self._model_op.create_or_update(model)

def _raise_if_not_evaluator(self, properties: Optional[Dict[str, Any]], message: str) -> None:
"""
:param properties: The properties of a model.
:type properties: dict[str, str]
:param message: The message to be set on exception.
:type message: str
:raises ~azure.ai.ml.exceptions.ValidationException: Raised if model is not an
evaluator.
"""
if properties is not None and not _is_evaluator(properties):
raise ResourceNotFoundError(
message=message,
response=None,
)

@monitor_with_activity(ops_logger, "Evaluator.Get", ActivityType.PUBLICAPI)
def get(self, name: str, version: Optional[str] = None, label: Optional[str] = None) -> Model:
"""Returns information about the specified model asset.

:param name: Name of the model.
:type name: str
:param version: Version of the model.
:type version: str
:param label: Label of the model. (mutually exclusive with version)
:type label: str
:raises ~azure.ai.ml.exceptions.ValidationException: Raised if Model cannot be successfully validated.
Details will be provided in the error message.
:return: Model asset object.
:rtype: ~azure.ai.ml.entities.Model
"""
model = self._model_op.get(name, version, label)

properties = None if model is None else model.properties
self._raise_if_not_evaluator(
properties,
f"Evaluator {name} with version {version} not found.",
)

return model

@monitor_with_activity(ops_logger, "Evaluator.Download", ActivityType.PUBLICAPI)
def download(self, name: str, version: str, download_path: Union[PathLike, str] = ".") -> None:
"""Download files related to a model.

:param name: Name of the model.
:type name: str
:param version: Version of the model.
:type version: str
:param download_path: Local path as download destination, defaults to current working directory of the current
user. Contents will be overwritten.
:type download_path: Union[PathLike, str]
:raises ResourceNotFoundError: if can't find a model matching provided name.
"""
self._model_op.download(name, version, download_path)

@monitor_with_activity(ops_logger, "Evaluator.List", ActivityType.PUBLICAPI)
def list(
self,
name: str,
stage: Optional[str] = None,
*,
list_view_type: ListViewType = ListViewType.ACTIVE_ONLY,
) -> Iterable[Model]:
"""List all model assets in workspace.

:param name: Name of the model.
:type name: str
:param stage: The Model stage
:type stage: Optional[str]
:keyword list_view_type: View type for including/excluding (for example) archived models.
Defaults to :attr:`ListViewType.ACTIVE_ONLY`.
:paramtype list_view_type: ListViewType
:return: An iterator like instance of Model objects
:rtype: ~azure.core.paging.ItemPaged[~azure.ai.ml.entities.Model]
"""
properties_str = "is-promptflow=true,is-evaluator=true"
if name:
return cast(
Iterable[Model],
(
self._model_op._model_versions_operation.list(
name=name,
registry_name=self._model_op._registry_name,
cls=lambda objs: [Model._from_rest_object(obj) for obj in objs],
properties=properties_str,
**self._model_op._scope_kwargs,
)
if self._registry_name
else self._model_op._model_versions_operation.list(
name=name,
workspace_name=self._model_op._workspace_name,
cls=lambda objs: [Model._from_rest_object(obj) for obj in objs],
list_view_type=list_view_type,
properties=properties_str,
stage=stage,
**self._model_op._scope_kwargs,
)
),
)
# ModelContainer object does not carry properties.
raise UnsupportedOperationError("list on evaluation operations without name provided")
# TODO: Implement filtering of the ModelContainerOperations list output
# return cast(
# Iterable[Model], (
# self._model_container_operation.list(
# registry_name=self._registry_name,
# cls=lambda objs: [Model._from_container_rest_object(obj) for obj in objs],
# list_view_type=list_view_type,
# **self._scope_kwargs,
# )
# if self._registry_name
# else self._model_container_operation.list(
# workspace_name=self._workspace_name,
# cls=lambda objs: [Model._from_container_rest_object(obj) for obj in objs],
# list_view_type=list_view_type,
# **self._scope_kwargs,
# )
# )
# )
Loading
Loading