diff --git a/src/sagemaker/workflow/_utils.py b/src/sagemaker/workflow/_utils.py index cfceb2d1e0..c5fd461602 100644 --- a/src/sagemaker/workflow/_utils.py +++ b/src/sagemaker/workflow/_utils.py @@ -55,6 +55,8 @@ def __init__( role, model_data: str, entry_point: str, + display_name: str = None, + description: str = None, source_dir: str = None, dependencies: List = None, depends_on: Union[List[str], List[Step]] = None, @@ -165,7 +167,12 @@ def __init__( # super! super(_RepackModelStep, self).__init__( - name=name, depends_on=depends_on, estimator=repacker, inputs=inputs + name=name, + display_name=display_name, + description=description, + depends_on=depends_on, + estimator=repacker, + inputs=inputs, ) def _prepare_for_repacking(self): @@ -285,6 +292,7 @@ def __init__( approval_status="PendingManualApproval", image_uri=None, compile_model_family=None, + display_name: str = None, description=None, depends_on: Union[List[str], List[Step]] = None, tags=None, @@ -326,7 +334,9 @@ def __init__( this step depends on **kwargs: additional arguments to `create_model`. """ - super(_RegisterModelStep, self).__init__(name, StepTypeEnum.REGISTER_MODEL, depends_on) + super(_RegisterModelStep, self).__init__( + name, display_name, description, StepTypeEnum.REGISTER_MODEL, depends_on + ) self.estimator = estimator self.model_data = model_data self.content_types = content_types diff --git a/src/sagemaker/workflow/callback_step.py b/src/sagemaker/workflow/callback_step.py index 6ec6152c2d..f88b56c9f5 100644 --- a/src/sagemaker/workflow/callback_step.py +++ b/src/sagemaker/workflow/callback_step.py @@ -83,6 +83,8 @@ def __init__( sqs_queue_url: str, inputs: dict, outputs: List[CallbackOutput], + display_name: str = None, + description: str = None, cache_config: CacheConfig = None, depends_on: Union[List[str], List[Step]] = None, ): @@ -94,11 +96,15 @@ def __init__( inputs (dict): Input arguments that will be provided in the SQS message body of callback messages. outputs (List[CallbackOutput]): Outputs that can be provided when completing a callback. + display_name (str): The display name of the callback step. + description (str): The description of the callback step. cache_config (CacheConfig): A `sagemaker.workflow.steps.CacheConfig` instance. depends_on (List[str] or List[Step]): A list of step names or step instances this `sagemaker.workflow.steps.CallbackStep` depends on """ - super(CallbackStep, self).__init__(name, StepTypeEnum.CALLBACK, depends_on) + super(CallbackStep, self).__init__( + name, display_name, description, StepTypeEnum.CALLBACK, depends_on + ) self.sqs_queue_url = sqs_queue_url self.outputs = outputs self.cache_config = cache_config diff --git a/src/sagemaker/workflow/condition_step.py b/src/sagemaker/workflow/condition_step.py index 0437ac34cb..a34330d94d 100644 --- a/src/sagemaker/workflow/condition_step.py +++ b/src/sagemaker/workflow/condition_step.py @@ -42,6 +42,8 @@ def __init__( self, name: str, depends_on: Union[List[str], List[Step]] = None, + display_name: str = None, + description: str = None, conditions: List[Condition] = None, if_steps: List[Union[Step, StepCollection]] = None, else_steps: List[Union[Step, StepCollection]] = None, @@ -53,6 +55,9 @@ def __init__( execution. Args: + name (str): The name of the condition step. + display_name (str): The display name of the condition step. + description (str): The description of the condition step. conditions (List[Condition]): A list of `sagemaker.workflow.conditions.Condition` instances. if_steps (List[Union[Step, StepCollection]]): A list of `sagemaker.workflow.steps.Step` @@ -62,7 +67,9 @@ def __init__( or `sagemaker.workflow.step_collections.StepCollection` instances that are marked as ready for execution if the list of conditions evaluates to False. """ - super(ConditionStep, self).__init__(name, StepTypeEnum.CONDITION, depends_on) + super(ConditionStep, self).__init__( + name, display_name, description, StepTypeEnum.CONDITION, depends_on + ) self.conditions = conditions or [] self.if_steps = if_steps or [] self.else_steps = else_steps or [] diff --git a/src/sagemaker/workflow/lambda_step.py b/src/sagemaker/workflow/lambda_step.py index bd223b27ba..968dd8dc0f 100644 --- a/src/sagemaker/workflow/lambda_step.py +++ b/src/sagemaker/workflow/lambda_step.py @@ -82,6 +82,8 @@ def __init__( self, name: str, lambda_func: Lambda, + display_name: str = None, + description: str = None, inputs: dict = None, outputs: List[LambdaOutput] = None, cache_config: CacheConfig = None, @@ -91,6 +93,8 @@ def __init__( Args: name (str): The name of the lambda step. + display_name (str): The display name of the Lambda step. + description (str): The description of the Lambda step. lambda_func (str): An instance of sagemaker.lambda_helper.Lambda. If lambda arn is specified in the instance, LambdaStep just invokes the function, else lambda function will be created while creating the pipeline. @@ -101,7 +105,9 @@ def __init__( depends_on (List[str]): A list of step names this `sagemaker.workflow.steps.LambdaStep` depends on """ - super(LambdaStep, self).__init__(name, StepTypeEnum.LAMBDA, depends_on) + super(LambdaStep, self).__init__( + name, display_name, description, StepTypeEnum.LAMBDA, depends_on + ) self.lambda_func = lambda_func self.outputs = outputs if outputs is not None else [] self.cache_config = cache_config diff --git a/src/sagemaker/workflow/step_collections.py b/src/sagemaker/workflow/step_collections.py index 0d28301323..0d2ed4bb9c 100644 --- a/src/sagemaker/workflow/step_collections.py +++ b/src/sagemaker/workflow/step_collections.py @@ -67,6 +67,7 @@ def __init__( approval_status=None, image_uri=None, compile_model_family=None, + display_name=None, description=None, tags=None, model: Union[Model, PipelineModel] = None, @@ -138,6 +139,8 @@ def __init__( tags=tags, subnets=subnets, security_group_ids=security_group_ids, + description=description, + display_name=display_name, **kwargs, ) steps.append(repack_model_step) @@ -179,6 +182,8 @@ def __init__( tags=tags, subnets=subnets, security_group_ids=security_group_ids, + description=description, + display_name=display_name, **kwargs, ) steps.append(repack_model_step) @@ -208,6 +213,7 @@ def __init__( image_uri=image_uri, compile_model_family=compile_model_family, description=description, + display_name=display_name, tags=tags, container_def_list=self.container_def_list, **kwargs, @@ -231,6 +237,8 @@ def __init__( instance_count, instance_type, transform_inputs, + description: str = None, + display_name: str = None, # model arguments image_uri=None, predictor_cls=None, @@ -302,6 +310,8 @@ def __init__( tags=tags, subnets=estimator.subnets, security_group_ids=estimator.security_group_ids, + description=description, + display_name=display_name, ) steps.append(repack_model_step) model_data = repack_model_step.properties.ModelArtifacts.S3ModelArtifacts @@ -324,6 +334,8 @@ def predict_wrapper(endpoint, session): name=f"{name}CreateModelStep", model=model, inputs=model_inputs, + description=description, + display_name=display_name, ) if "entry_point" not in kwargs and depends_on: # if the CreateModelStep is the first step in the collection @@ -351,6 +363,8 @@ def predict_wrapper(endpoint, session): name=f"{name}TransformStep", transformer=transformer, inputs=transform_inputs, + description=description, + display_name=display_name, ) steps.append(transform_step) diff --git a/src/sagemaker/workflow/steps.py b/src/sagemaker/workflow/steps.py index e816d2b8fc..587c638c67 100644 --- a/src/sagemaker/workflow/steps.py +++ b/src/sagemaker/workflow/steps.py @@ -63,12 +63,16 @@ class Step(Entity): Attributes: name (str): The name of the step. + display_name (str): The display name of the step. + description (str): The description of the step. step_type (StepTypeEnum): The type of the step. depends_on (List[str] or List[Step]): The list of step names or step instances the current step depends on """ name: str = attr.ib(factory=str) + display_name: str = attr.ib(default=None) + description: str = attr.ib(default=None) step_type: StepTypeEnum = attr.ib(factory=StepTypeEnum.factory) depends_on: Union[List[str], List["Step"]] = attr.ib(default=None) @@ -91,7 +95,10 @@ def to_request(self) -> RequestType: } if self.depends_on: request_dict["DependsOn"] = self._resolve_depends_on(self.depends_on) - + if self.display_name: + request_dict["DisplayName"] = self.display_name + if self.description: + request_dict["Description"] = self.description return request_dict def add_depends_on(self, step_names: Union[List[str], List["Step"]]): @@ -168,6 +175,8 @@ def __init__( self, name: str, estimator: EstimatorBase, + display_name: str = None, + description: str = None, inputs: Union[TrainingInput, dict, str, FileSystemInput] = None, cache_config: CacheConfig = None, depends_on: Union[List[str], List[Step]] = None, @@ -180,6 +189,8 @@ def __init__( Args: name (str): The name of the training step. estimator (EstimatorBase): A `sagemaker.estimator.EstimatorBase` instance. + display_name (str): The display name of the training step. + description (str): The description of the training step. inputs (Union[str, dict, TrainingInput, FileSystemInput]): Information about the training data. This can be one of three types: @@ -200,7 +211,9 @@ def __init__( depends_on (List[str] or List[Step]): A list of step names or step instances this `sagemaker.workflow.steps.TrainingStep` depends on """ - super(TrainingStep, self).__init__(name, StepTypeEnum.TRAINING, depends_on) + super(TrainingStep, self).__init__( + name, display_name, description, StepTypeEnum.TRAINING, depends_on + ) self.estimator = estimator self.inputs = inputs self._properties = Properties( @@ -248,6 +261,8 @@ def __init__( model: Model, inputs: CreateModelInput, depends_on: Union[List[str], List[Step]] = None, + display_name: str = None, + description: str = None, ): """Construct a CreateModelStep, given an `sagemaker.model.Model` instance. @@ -261,8 +276,12 @@ def __init__( Defaults to `None`. depends_on (List[str] or List[Step]): A list of step names or step instances this `sagemaker.workflow.steps.CreateModelStep` depends on + display_name (str): The display name of the CreateModel step. + description (str): The description of the CreateModel step. """ - super(CreateModelStep, self).__init__(name, StepTypeEnum.CREATE_MODEL, depends_on) + super(CreateModelStep, self).__init__( + name, display_name, description, StepTypeEnum.CREATE_MODEL, depends_on + ) self.model = model self.inputs = inputs or CreateModelInput() @@ -304,6 +323,8 @@ def __init__( name: str, transformer: Transformer, inputs: TransformInput, + display_name: str = None, + description: str = None, cache_config: CacheConfig = None, depends_on: Union[List[str], List[Step]] = None, ): @@ -317,10 +338,14 @@ def __init__( transformer (Transformer): A `sagemaker.transformer.Transformer` instance. inputs (TransformInput): A `sagemaker.inputs.TransformInput` instance. cache_config (CacheConfig): A `sagemaker.workflow.steps.CacheConfig` instance. - depends_on (List[str] or List[Step]): A list of step names or step instances - this `sagemaker.workflow.steps.TransformStep` depends on + display_name (str): The display name of the transform step. + description (str): The description of the transform step. + depends_on (List[str]): A list of step names this `sagemaker.workflow.steps.TransformStep` + depends on """ - super(TransformStep, self).__init__(name, StepTypeEnum.TRANSFORM, depends_on) + super(TransformStep, self).__init__( + name, display_name, description, StepTypeEnum.TRANSFORM, depends_on + ) self.transformer = transformer self.inputs = inputs self.cache_config = cache_config @@ -375,6 +400,8 @@ def __init__( self, name: str, processor: Processor, + display_name: str = None, + description: str = None, inputs: List[ProcessingInput] = None, outputs: List[ProcessingOutput] = None, job_arguments: List[str] = None, @@ -391,6 +418,8 @@ def __init__( Args: name (str): The name of the processing step. processor (Processor): A `sagemaker.processing.Processor` instance. + display_name (str): The display name of the processing step. + description (str): The description of the processing step. inputs (List[ProcessingInput]): A list of `sagemaker.processing.ProcessorInput` instances. Defaults to `None`. outputs (List[ProcessingOutput]): A list of `sagemaker.processing.ProcessorOutput` @@ -405,7 +434,9 @@ def __init__( depends_on (List[str] or List[Step]): A list of step names or step instance this `sagemaker.workflow.steps.ProcessingStep` depends on """ - super(ProcessingStep, self).__init__(name, StepTypeEnum.PROCESSING, depends_on) + super(ProcessingStep, self).__init__( + name, display_name, description, StepTypeEnum.PROCESSING, depends_on + ) self.processor = processor self.inputs = inputs self.outputs = outputs @@ -468,6 +499,8 @@ def __init__( self, name: str, tuner: HyperparameterTuner, + display_name: str = None, + description: str = None, inputs=None, job_arguments: List[str] = None, cache_config: CacheConfig = None, @@ -481,6 +514,8 @@ def __init__( Args: name (str): The name of the tuning step. tuner (HyperparameterTuner): A `sagemaker.tuner.HyperparameterTuner` instance. + display_name (str): The display name of the tuning step. + description (str): The description of the tuning step. inputs: Information about the training data. Please refer to the ``fit()`` method of the associated estimator, as this can take any of the following forms: @@ -514,7 +549,9 @@ def __init__( depends_on (List[str] or List[Step]): A list of step names or step instance this `sagemaker.workflow.steps.ProcessingStep` depends on """ - super(TuningStep, self).__init__(name, StepTypeEnum.TUNING, depends_on) + super(TuningStep, self).__init__( + name, display_name, description, StepTypeEnum.TUNING, depends_on + ) self.tuner = tuner self.inputs = inputs self.job_arguments = job_arguments diff --git a/tests/integ/test_workflow.py b/tests/integ/test_workflow.py index 1592c0d9eb..ade72c74a0 100644 --- a/tests/integ/test_workflow.py +++ b/tests/integ/test_workflow.py @@ -285,6 +285,8 @@ def test_three_step_definition( ) step_process = ProcessingStep( name="my-process", + display_name="ProcessingStep", + description="description for Processing step", processor=sklearn_processor, inputs=[ ProcessingInput(source=input_data, destination="/opt/ml/processing/input"), @@ -319,6 +321,8 @@ def test_three_step_definition( ) step_train = TrainingStep( name="my-train", + display_name="TrainingStep", + description="description for Training step", estimator=sklearn_train, inputs=TrainingInput( s3_data=step_process.properties.ProcessingOutputConfig.Outputs[ @@ -339,6 +343,8 @@ def test_three_step_definition( ) step_model = CreateModelStep( name="my-model", + display_name="ModelStep", + description="description for Model step", model=model, inputs=model_inputs, ) @@ -367,10 +373,12 @@ def test_three_step_definition( assert len(steps) == 3 names_and_types = [] + display_names_and_desc = [] processing_args = {} training_args = {} for step in steps: names_and_types.append((step["Name"], step["Type"])) + display_names_and_desc.append((step["DisplayName"], step["Description"])) if step["Type"] == "Processing": processing_args = step["Arguments"] if step["Type"] == "Training": @@ -386,6 +394,13 @@ def test_three_step_definition( ] ) + assert set(display_names_and_desc) == set( + [ + ("ProcessingStep", "description for Processing step"), + ("TrainingStep", "description for Training step"), + ("ModelStep", "description for Model step"), + ] + ) assert processing_args["ProcessingResources"]["ClusterConfig"] == { "InstanceType": {"Get": "Parameters.InstanceType"}, "InstanceCount": {"Get": "Parameters.InstanceCount"}, diff --git a/tests/unit/sagemaker/workflow/test_condition_step.py b/tests/unit/sagemaker/workflow/test_condition_step.py index 6deadb6173..abfbf590fa 100644 --- a/tests/unit/sagemaker/workflow/test_condition_step.py +++ b/tests/unit/sagemaker/workflow/test_condition_step.py @@ -24,8 +24,8 @@ class CustomStep(Step): - def __init__(self, name): - super(CustomStep, self).__init__(name, StepTypeEnum.TRAINING) + def __init__(self, name, display_name=None, description=None): + super(CustomStep, self).__init__(name, display_name, description, StepTypeEnum.TRAINING) self._properties = Properties(path=f"Steps.{name}") @property @@ -40,8 +40,8 @@ def properties(self): def test_condition_step(): param = ParameterInteger(name="MyInt") cond = ConditionEquals(left=param, right=1) - step1 = CustomStep("MyStep1") - step2 = CustomStep("MyStep2") + step1 = CustomStep(name="MyStep1") + step2 = CustomStep(name="MyStep2") cond_step = ConditionStep( name="MyConditionStep", depends_on=["TestStep"], diff --git a/tests/unit/sagemaker/workflow/test_lambda_step.py b/tests/unit/sagemaker/workflow/test_lambda_step.py index bcc69f310f..4149f210da 100644 --- a/tests/unit/sagemaker/workflow/test_lambda_step.py +++ b/tests/unit/sagemaker/workflow/test_lambda_step.py @@ -48,6 +48,8 @@ def test_lambda_step(sagemaker_session): function_arn="arn:aws:lambda:us-west-2:123456789012:function:sagemaker_test_lambda", session=sagemaker_session, ), + display_name="MyLambdaStep", + description="MyLambdaStepDescription", inputs={"arg1": "foo", "arg2": 5, "arg3": param}, outputs=[outputParam1, outputParam2], ) @@ -56,6 +58,8 @@ def test_lambda_step(sagemaker_session): "Name": "MyLambdaStep", "Type": "Lambda", "DependsOn": ["TestStep", "SecondTestStep"], + "DisplayName": "MyLambdaStep", + "Description": "MyLambdaStepDescription", "FunctionArn": "arn:aws:lambda:us-west-2:123456789012:function:sagemaker_test_lambda", "OutputParameters": [ {"OutputName": "output1", "OutputType": "String"}, diff --git a/tests/unit/sagemaker/workflow/test_pipeline.py b/tests/unit/sagemaker/workflow/test_pipeline.py index e69420ccb2..4b68abceeb 100644 --- a/tests/unit/sagemaker/workflow/test_pipeline.py +++ b/tests/unit/sagemaker/workflow/test_pipeline.py @@ -37,9 +37,9 @@ class CustomStep(Step): - def __init__(self, name, input_data): + def __init__(self, name, input_data, display_name=None, description=None): self.input_data = input_data - super(CustomStep, self).__init__(name, StepTypeEnum.TRAINING) + super(CustomStep, self).__init__(name, display_name, description, StepTypeEnum.TRAINING) path = f"Steps.{name}" prop = Properties(path=path) diff --git a/tests/unit/sagemaker/workflow/test_step_collections.py b/tests/unit/sagemaker/workflow/test_step_collections.py index 3412152489..29540ae10f 100644 --- a/tests/unit/sagemaker/workflow/test_step_collections.py +++ b/tests/unit/sagemaker/workflow/test_step_collections.py @@ -59,8 +59,8 @@ class CustomStep(Step): - def __init__(self, name): - super(CustomStep, self).__init__(name, StepTypeEnum.TRAINING) + def __init__(self, name, display_name=None, description=None): + super(CustomStep, self).__init__(name, display_name, description, StepTypeEnum.TRAINING) self._properties = Properties(path=f"Steps.{name}") @property @@ -208,6 +208,7 @@ def test_register_model(estimator, model_metrics): model_metrics=model_metrics, approval_status="Approved", description="description", + display_name="RegisterModelStep", depends_on=["TestStep"], tags=[{"Key": "myKey", "Value": "myValue"}], ) @@ -217,6 +218,8 @@ def test_register_model(estimator, model_metrics): "Name": "RegisterModelStep", "Type": "RegisterModel", "DependsOn": ["TestStep"], + "DisplayName": "RegisterModelStep", + "Description": "description", "Arguments": { "InferenceSpecification": { "Containers": [ @@ -265,6 +268,7 @@ def test_register_model_tf(estimator_tf, model_metrics): { "Name": "RegisterModelStep", "Type": "RegisterModel", + "Description": "description", "Arguments": { "InferenceSpecification": { "Containers": [ @@ -322,6 +326,7 @@ def test_register_model_sip(estimator, model_metrics): { "Name": "RegisterModelStep", "Type": "RegisterModel", + "Description": "description", "DependsOn": ["TestStep"], "Arguments": { "InferenceSpecification": { diff --git a/tests/unit/sagemaker/workflow/test_steps.py b/tests/unit/sagemaker/workflow/test_steps.py index 6d7fe04cb5..839ad6a814 100644 --- a/tests/unit/sagemaker/workflow/test_steps.py +++ b/tests/unit/sagemaker/workflow/test_steps.py @@ -67,8 +67,8 @@ class CustomStep(Step): - def __init__(self, name): - super(CustomStep, self).__init__(name, StepTypeEnum.TRAINING) + def __init__(self, name, display_name=None, description=None): + super(CustomStep, self).__init__(name, display_name, description, StepTypeEnum.TRAINING) self._properties = Properties(path=f"Steps.{name}") @property @@ -121,8 +121,36 @@ def sagemaker_session(boto_session, client): def test_custom_step(): - step = CustomStep("MyStep") - assert step.to_request() == {"Name": "MyStep", "Type": "Training", "Arguments": dict()} + step = CustomStep( + name="MyStep", display_name="CustomStepDisplayName", description="CustomStepDescription" + ) + assert step.to_request() == { + "Name": "MyStep", + "DisplayName": "CustomStepDisplayName", + "Description": "CustomStepDescription", + "Type": "Training", + "Arguments": dict(), + } + + +def test_custom_step_without_display_name(): + step = CustomStep(name="MyStep", description="CustomStepDescription") + assert step.to_request() == { + "Name": "MyStep", + "Description": "CustomStepDescription", + "Type": "Training", + "Arguments": dict(), + } + + +def test_custom_step_without_description(): + step = CustomStep(name="MyStep", display_name="CustomStepDisplayName") + assert step.to_request() == { + "Name": "MyStep", + "DisplayName": "CustomStepDisplayName", + "Type": "Training", + "Arguments": dict(), + } def test_training_step_base_estimator(sagemaker_session): @@ -151,6 +179,8 @@ def test_training_step_base_estimator(sagemaker_session): step = TrainingStep( name="MyTrainingStep", depends_on=["TestStep"], + description="TrainingStep description", + display_name="MyTrainingStep", estimator=estimator, inputs=inputs, cache_config=cache_config, @@ -159,6 +189,8 @@ def test_training_step_base_estimator(sagemaker_session): assert step.to_request() == { "Name": "MyTrainingStep", "Type": "Training", + "Description": "TrainingStep description", + "DisplayName": "MyTrainingStep", "DependsOn": ["TestStep", "AnotherTestStep"], "Arguments": { "AlgorithmSpecification": {"TrainingImage": IMAGE_URI, "TrainingInputMode": "File"}, @@ -304,6 +336,8 @@ def test_processing_step(sagemaker_session): cache_config = CacheConfig(enable_caching=True, expire_after="PT1H") step = ProcessingStep( name="MyProcessingStep", + description="ProcessingStep description", + display_name="MyProcessingStep", depends_on=["TestStep", "SecondTestStep"], processor=processor, inputs=inputs, @@ -313,6 +347,8 @@ def test_processing_step(sagemaker_session): step.add_depends_on(["ThirdTestStep"]) assert step.to_request() == { "Name": "MyProcessingStep", + "Description": "ProcessingStep description", + "DisplayName": "MyProcessingStep", "Type": "Processing", "DependsOn": ["TestStep", "SecondTestStep", "ThirdTestStep"], "Arguments": { @@ -415,6 +451,8 @@ def test_create_model_step(sagemaker_session): step = CreateModelStep( name="MyCreateModelStep", depends_on=["TestStep"], + display_name="MyCreateModelStep", + description="TestDescription", model=model, inputs=inputs, ) @@ -423,6 +461,8 @@ def test_create_model_step(sagemaker_session): assert step.to_request() == { "Name": "MyCreateModelStep", "Type": "Model", + "Description": "TestDescription", + "DisplayName": "MyCreateModelStep", "DependsOn": ["TestStep", "SecondTestStep"], "Arguments": { "ExecutionRoleArn": "DummyRole", @@ -445,6 +485,8 @@ def test_transform_step(sagemaker_session): name="MyTransformStep", depends_on=["TestStep"], transformer=transformer, + display_name="TransformStep", + description="TestDescription", inputs=inputs, cache_config=cache_config, ) @@ -452,6 +494,8 @@ def test_transform_step(sagemaker_session): assert step.to_request() == { "Name": "MyTransformStep", "Type": "Transform", + "Description": "TestDescription", + "DisplayName": "TransformStep", "DependsOn": ["TestStep", "SecondTestStep"], "Arguments": { "ModelName": "gisele",