Skip to content

Commit a5b30c4

Browse files
Rishabh0255Rishabh Devnani
andauthored
refactor: Remove Bedrock step types and clean up step test modules (#6223)
* refactor: Remove Bedrock step types and clean up step test modules Removes the four Bedrock pipeline step classes (BedrockCustomModelStep, BedrockCustomModelDeploymentStep, BedrockModelImportStep, BedrockProvisionedModelThroughputStep) and their StepTypeEnum values pending re-evaluation of SDK support for these step types. They can be reintroduced later if needed. The remaining step types (EndpointConfigStep, EndpointStep, InferenceComponentStep, LineageStep) are unchanged. Also renames the step test modules to descriptive names (test_inference_lineage_steps.py, test_lineage_step.py) and removes the now-unused camelCase-to-PascalCase member-name conversion from the argument validation helper (it existed only for the Bedrock APIs). --- X-AI-Prompt: Follow-up on the merged v2 backport: remove Bedrock step types and internal-codename file names X-AI-Tool: kiro-cli * refactor(pipeline): Align inference and lineage steps with v3 Brings the four step types from #6153 onto the same design as v3, so both majors present one API. These steps are not in use yet, so the change is made directly rather than with a deprecation path. Steps now take step_args captured under a PipelineSession instead of a raw arguments dict, following the convention used by TrainingStep and ModelStep. Capture goes through one seam: PipelineSession._intercept_create_request returns the captured context, so a producer returns its result directly. Producers, all now routed through that seam: - Session.create_endpoint_config, which creates only the endpoint configuration. It gained an optional production_variants argument, appended last so existing callers are unaffected, because an inference-component endpoint needs a variant with no model name that the single-model form cannot express. - Session.create_endpoint and Session.create_inference_component. - Action.create(), Artifact.create() and Context.create() for lineage, whose requests Record._invoke_api now routes through the same seam. LineageStep takes actions, artifacts, contexts and associations. A step creates a batch of entities and its associations reference them by name and type, or by ARN for an entity created elsewhere. This replaces the cross-step Steps.<Name>.ActionArns['<name>'] form, which could not resolve: the service matches an association endpoint against the entities created by the same step, so a reference to another step's entity fails the execution. Two fixes fall out of the seam change: PipelineModel.register() tested the intercept result with `"ModelPackageArn" in model_package`, which raises TypeError once that result is a step arguments object rather than None. It now checks for a dict first. The lineage unit tests used a bare Mock session, whose auto-mocked _intercept_create_request swallowed the request so the client was never called. The double now invokes the callable, as the real base Session does. _argument_validation.py is deleted. With the Bedrock steps removed by the previous commit and the four remaining steps on step_args, nothing uses it. Testing: 39 unit tests for the four steps. The lineage, step, pipeline, pipeline session and pipeline model suites were run against the previous commit and this one with identical results. The integration tests are ported but were not run against the service on this branch; the equivalent v3 tests pass end to end. --- X-AI-Prompt: Re-port v2 onto the new design X-AI-Tool: kiro-cli * fix(pipeline): Let create_endpoint_config build an IC endpoint config Found by running the deployment integration test against SageMaker. An inference-component endpoint config carries a production variant with no model name, and therefore needs ExecutionRoleArn in place of a model. create_endpoint_config had no way to supply it, so the test failed with "unexpected keyword argument 'role'". A role argument is added, appended last so existing callers are unaffected, applying the same rule endpoint_from_production_variants already uses: when no variant names a model, expand the role and set ExecutionRoleArn. The KMS check then failed too, because instance_supports_kms() was called with the instance_type that explicit variants leave as None. It now derives KMS support from the variants in that case, as v3 does, and keeps using instance_type when one is given. Also moves the deployment test's resource creation inside its try, so a failure between creating the model and starting the pipeline still reaches cleanup. The two failures above leaked a model and an S3 object each, because both were created before the try began. Testing: both integration tests now pass against SageMaker -- lineage in 11.70s and the chained EndpointConfig -> Endpoint -> InferenceComponent pipeline in 396.23s, with the endpoint and component observed InService and no resources left behind. The 25 existing create_endpoint_config and endpoint_from_production_variants unit tests still pass, confirming the single-model path is unchanged. --- X-AI-Prompt: Run the v2 integ tests against the service, also make sure you are removing the old code and adding the new code X-AI-Tool: kiro-cli * fix(tests): Use the shared session fixtures in the new integ tests Both new integ tests defined local sagemaker_session and pipeline_session fixtures returning a bare Session() / PipelineSession(), shadowing the shared ones. CI runs the integ suite with AWS_DEFAULT_REGION unset, so a bare session has no region and both tests errored at setup with "Must setup local AWS configuration with a region supported by SageMaker". Reported by @jam-jee on the PR. The local role fixture went too: the workflow conftest already provides one. The shared fixtures build on a boto3.Session carrying an explicit region, which is exactly what was missing. My earlier runs passed only because the harness exported AWS_DEFAULT_REGION, which hid this. Both tests have now been re-run with that variable unset, matching CI: lineage in 11.83s and the chained EndpointConfig -> Endpoint -> InferenceComponent pipeline in 411.04s, with no resources left behind. --- X-AI-Prompt: did you read the comment on the pr? X-AI-Tool: kiro-cli --------- Co-authored-by: Rishabh Devnani <rdevnani@amazon.com>
1 parent 88dbcc2 commit a5b30c4

17 files changed

Lines changed: 1582 additions & 1230 deletions

‎src/sagemaker/apiutils/_base_types.py‎

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,27 @@ def with_boto(self, boto_dict):
219219
)
220220
return self
221221

222+
# Lineage entity creates that a pipeline step can compose rather than execute.
223+
# Requests for these are routed through the session's
224+
# ``_intercept_create_request`` seam, so a ``PipelineSession`` captures them as
225+
# step arguments while a plain ``Session`` calls the service. Used by
226+
# ``sagemaker.workflow.lineage_step.LineageStep``.
227+
_PIPELINE_CAPTURABLE_METHODS = frozenset(
228+
{"create_action", "create_artifact", "create_context", "add_association"}
229+
)
230+
222231
def _invoke_api(self, boto_method, boto_method_members):
223232
"""Invoke a SageMaker API."""
224233
api_values = {k: v for k, v in vars(self).items() if k in boto_method_members}
225234
api_kwargs = self.to_boto(api_values)
226-
api_method = getattr(self.sagemaker_session.sagemaker_client, boto_method)
227-
api_boto_response = api_method(**api_kwargs)
228-
return self.with_boto(api_boto_response)
235+
236+
def submit(request):
237+
api_method = getattr(self.sagemaker_session.sagemaker_client, boto_method)
238+
return self.with_boto(api_method(**request))
239+
240+
if boto_method in self._PIPELINE_CAPTURABLE_METHODS:
241+
return self.sagemaker_session._intercept_create_request(
242+
api_kwargs, submit, boto_method
243+
)
244+
245+
return submit(api_kwargs)

‎src/sagemaker/pipeline.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ def register(
485485
**model_pkg_args
486486
)
487487

488-
if model_package is not None and "ModelPackageArn" in model_package:
488+
if isinstance(model_package, dict) and "ModelPackageArn" in model_package:
489489
return ModelPackage(
490490
role=self.role,
491491
model_package_arn=model_package.get("ModelPackageArn"),

‎src/sagemaker/session.py‎

Lines changed: 100 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4857,9 +4857,9 @@ def describe_model(self, name):
48574857
def create_endpoint_config(
48584858
self,
48594859
name,
4860-
model_name,
4861-
initial_instance_count,
4862-
instance_type,
4860+
model_name=None,
4861+
initial_instance_count=None,
4862+
instance_type=None,
48634863
accelerator_type=None,
48644864
tags=None,
48654865
kms_key=None,
@@ -4872,6 +4872,8 @@ def create_endpoint_config(
48724872
serverless_inference_config_dict=None,
48734873
routing_config: Optional[Dict[str, Any]] = None,
48744874
inference_ami_version: Optional[str] = None,
4875+
production_variants: Optional[List[Dict[str, Any]]] = None,
4876+
role: Optional[str] = None,
48754877
):
48764878
"""Create an Amazon SageMaker endpoint configuration.
48774879
@@ -4933,6 +4935,16 @@ def create_endpoint_config(
49334935
Specifies an option from a collection of preconfigured
49344936
Amazon Machine Image (AMI) images. For a full list of options, see:
49354937
https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_ProductionVariant.html
4938+
production_variants (Optional[List[Dict[str, Any]]]): Explicit
4939+
production variants to use as-is. When given, ``model_name``,
4940+
``initial_instance_count`` and ``instance_type`` are ignored and
4941+
no variant is built. Use this for a variant a single-model
4942+
configuration cannot express, such as an inference-component
4943+
endpoint whose variant carries no model name.
4944+
role (Optional[str]): An AWS IAM role, name or full ARN. Required
4945+
when no production variant names a model, as with an
4946+
inference-component endpoint: the endpoint config then carries
4947+
``ExecutionRoleArn`` in place of a model.
49364948
49374949
Example:
49384950
>>> tags = [{'Key': 'tagname', 'Value': 'tagvalue'}]
@@ -4942,24 +4954,25 @@ def create_endpoint_config(
49424954
.Client.add_tags
49434955
49444956
Returns:
4945-
str: Name of the endpoint point configuration created.
4957+
str: Name of the endpoint point configuration created. Under a
4958+
``PipelineSession`` the captured step arguments are returned instead
4959+
and no service call is made.
49464960
"""
4947-
logger.info("Creating endpoint-config with name %s", name)
4948-
49494961
tags = format_tags(tags) or []
4950-
provided_production_variant = production_variant(
4951-
model_name,
4952-
instance_type,
4953-
initial_instance_count,
4954-
accelerator_type=accelerator_type,
4955-
serverless_inference_config=serverless_inference_config_dict,
4956-
volume_size=volume_size,
4957-
model_data_download_timeout=model_data_download_timeout,
4958-
container_startup_health_check_timeout=container_startup_health_check_timeout,
4959-
routing_config=routing_config,
4960-
inference_ami_version=inference_ami_version,
4961-
)
4962-
production_variants = [provided_production_variant]
4962+
if production_variants is None:
4963+
provided_production_variant = production_variant(
4964+
model_name,
4965+
instance_type,
4966+
initial_instance_count,
4967+
accelerator_type=accelerator_type,
4968+
serverless_inference_config=serverless_inference_config_dict,
4969+
volume_size=volume_size,
4970+
model_data_download_timeout=model_data_download_timeout,
4971+
container_startup_health_check_timeout=container_startup_health_check_timeout,
4972+
routing_config=routing_config,
4973+
inference_ami_version=inference_ami_version,
4974+
)
4975+
production_variants = [provided_production_variant]
49634976
# Currently we just inject CoreDumpConfig.KmsKeyId from the config for production variant.
49644977
# But if that parameter is injected, then CoreDumpConfig.DestinationS3Uri needs to be
49654978
# present.
@@ -4975,17 +4988,43 @@ def create_endpoint_config(
49754988
"ProductionVariants": production_variants,
49764989
}
49774990

4991+
role = resolve_value_from_config(
4992+
role,
4993+
ENDPOINT_CONFIG_EXECUTION_ROLE_ARN_PATH,
4994+
sagemaker_session=self,
4995+
)
4996+
# For an Amazon SageMaker inference-component based endpoint, no Model name is
4997+
# passed during endpoint creation. ExecutionRoleArn is needed in the endpoint
4998+
# config instead, so that the Endpoint can be created.
4999+
model_names = [pv["ModelName"] for pv in production_variants if "ModelName" in pv]
5000+
if len(model_names) == 0:
5001+
# The SDK allows deploying with a role name rather than a full ARN, so
5002+
# expand it here.
5003+
role = self.expand_role(role)
5004+
request["ExecutionRoleArn"] = role
5005+
49785006
tags = _append_project_tags(tags)
49795007
tags = self._append_sagemaker_config_tags(
49805008
tags, "{}.{}.{}".format(SAGEMAKER, ENDPOINT_CONFIG, TAGS)
49815009
)
49825010
if tags is not None:
49835011
request["Tags"] = tags
5012+
# When explicit variants are supplied there is no single instance_type to
5013+
# consult, so derive KMS support from the variants themselves, as
5014+
# endpoint_from_production_variants does.
5015+
if instance_type is not None:
5016+
supports_kms = instance_supports_kms(instance_type)
5017+
else:
5018+
supports_kms = any(
5019+
instance_supports_kms(pv["InstanceType"])
5020+
for pv in production_variants
5021+
if "InstanceType" in pv
5022+
)
49845023
kms_key = (
49855024
resolve_value_from_config(
49865025
kms_key, ENDPOINT_CONFIG_KMS_KEY_ID_PATH, sagemaker_session=self
49875026
)
4988-
if instance_supports_kms(instance_type)
5027+
if supports_kms
49895028
else kms_key
49905029
)
49915030
if kms_key is not None:
@@ -5008,8 +5047,14 @@ def create_endpoint_config(
50085047
if explainer_config_dict is not None:
50095048
request["ExplainerConfig"] = explainer_config_dict
50105049

5011-
self.sagemaker_client.create_endpoint_config(**request)
5012-
return name
5050+
def submit(request):
5051+
logger.info("Creating endpoint-config with name %s", name)
5052+
self.sagemaker_client.create_endpoint_config(**request)
5053+
return name
5054+
5055+
return self._intercept_create_request(
5056+
request, submit, self.create_endpoint_config.__name__
5057+
)
50135058

50145059
def create_endpoint_config_from_existing(
50155060
self,
@@ -5169,29 +5214,39 @@ def create_endpoint(self, endpoint_name, config_name, tags=None, wait=True, live
51695214
(default: None).
51705215
51715216
Returns:
5172-
str: Name of the Amazon SageMaker ``Endpoint`` created.
5217+
str: Name of the Amazon SageMaker ``Endpoint`` created. Under a
5218+
``PipelineSession`` the captured step arguments are returned instead
5219+
and no service call is made.
51735220
51745221
Raises:
51755222
botocore.exceptions.ClientError: If Sagemaker throws an exception while creating
51765223
endpoint.
51775224
"""
5178-
logger.info("Creating endpoint with name %s", endpoint_name)
5179-
51805225
tags = format_tags(tags) or []
51815226
tags = _append_project_tags(tags)
51825227
tags = self._append_sagemaker_config_tags(
51835228
tags, "{}.{}.{}".format(SAGEMAKER, ENDPOINT, TAGS)
51845229
)
5185-
try:
5186-
res = self.sagemaker_client.create_endpoint(
5187-
EndpointName=endpoint_name, EndpointConfigName=config_name, Tags=tags
5188-
)
5230+
create_endpoint_request = {
5231+
"EndpointName": endpoint_name,
5232+
"EndpointConfigName": config_name,
5233+
"Tags": tags,
5234+
}
5235+
5236+
def submit(request):
5237+
logger.info("Creating endpoint with name %s", endpoint_name)
5238+
res = self.sagemaker_client.create_endpoint(**request)
51895239
if res:
51905240
self.endpoint_arn = res["EndpointArn"]
51915241

51925242
if wait:
51935243
self.wait_for_endpoint(endpoint_name, live_logging=live_logging)
51945244
return endpoint_name
5245+
5246+
try:
5247+
return self._intercept_create_request(
5248+
create_endpoint_request, submit, self.create_endpoint.__name__
5249+
)
51955250
except Exception as e:
51965251
troubleshooting = (
51975252
"https://docs.aws.amazon.com/sagemaker/latest/dg/"
@@ -5361,13 +5416,9 @@ def create_inference_component(
53615416
53625417
Returns:
53635418
str: Name of the Amazon SageMaker ``InferenceComponent`` if created.
5419+
Under a ``PipelineSession`` the captured step arguments are returned
5420+
instead and no service call is made.
53645421
"""
5365-
LOGGER.info(
5366-
"Creating inference component with name %s for endpoint %s",
5367-
inference_component_name,
5368-
endpoint_name,
5369-
)
5370-
53715422
if runtime_config is None:
53725423
runtime_config = {"CopyCount": 1}
53735424

@@ -5387,10 +5438,20 @@ def create_inference_component(
53875438
if tags and len(tags) != 0:
53885439
request["Tags"] = tags
53895440

5390-
self.sagemaker_client.create_inference_component(**request)
5391-
if wait:
5392-
self.wait_for_inference_component(inference_component_name)
5393-
return inference_component_name
5441+
def submit(request):
5442+
LOGGER.info(
5443+
"Creating inference component with name %s for endpoint %s",
5444+
inference_component_name,
5445+
endpoint_name,
5446+
)
5447+
self.sagemaker_client.create_inference_component(**request)
5448+
if wait:
5449+
self.wait_for_inference_component(inference_component_name)
5450+
return inference_component_name
5451+
5452+
return self._intercept_create_request(
5453+
request, submit, self.create_inference_component.__name__
5454+
)
53945455

53955456
def wait_for_inference_component(self, inference_component_name, poll=20):
53965457
"""Wait for an Amazon SageMaker ``Inference Component`` deployment to complete.

0 commit comments

Comments
 (0)