Repository navigation
fix: emit boto-style tag keys in ModelTrainer pipeline requests - #6485
Conversation
_create_training_job_args() called model_dump() on each Tag, which yields the
model's own lowercase field names. serialize() renames the fields of Tag models
to the boto-style {"Key": ..., "Value": ...} form the service requires, but
passes plain dicts through untouched, so the dump happened too early and the
pipeline definition kept lowercase keys:
"Tags": [{"key": "project", "value": "ml-project"}]
CreatePipeline rejected the definition with "Unable to parse pipeline
definition. Unknown Argument member 'key'". The boto3 payload that
TrainingQueue submits to AWS Batch carried the same invalid tags.
Hand the validated Tag objects to the branch that serializes the request. The
interactive request keeps the dicts, because TrainingJob.create annotates its
tags parameter with sagemaker.core.resources.Tag, a resource class that shadows
the shape of the same name, so a sagemaker.core.shapes.Tag instance fails its
validation while the dicts are revalidated into it.
Fixes aws#6474
There was a problem hiding this comment.
🤖 AI code review
This PR fixes ModelTrainer._create_training_job_args so that tags in the boto3/PipelineSession serialization branch are handed the validated Tag model objects (self.tags) rather than the pre-dumped snake_case dicts. Since serialize/_serialize_shape only renames field names (snake→Pascal) for shape instances and leaves plain dicts untouched, this is what produces the required {"Key": ..., "Value": ...} casing that CreatePipeline and AWS Batch expect. I verified Tag resolves to shapes.Tag with key/value fields, that _serialize_shape performs the PascalCase conversion, that the non-boto3 interactive path still uses the dicts, and that the new tests' imports/constants all exist in the base checkout. The change is correct, minimal, well-tested, and consistent with prior precedent (PR #6415). No issues found.
Reviewed commit 178a161. Automated review; verify before acting.
Issue #, if available: Fixes #6474
Description
ModelTraineremitted invalid lowercase tag keys into pipeline definitions, andCreatePipelinerejected them withUnable to parse pipeline definition. Unknown Argument member 'key'._create_training_job_args()calledmodel_dump()on each tag, which yields the model's own field names (key/value— the v3Tagshape has no aliases).serialize()renames the fields ofTagmodels to the boto-style{"Key": ..., "Value": ...}form the service requires, but passes plain dicts through untouched:So the dump happened one step too early and the casing was never fixed. This change hands the validated
Tagobjects to the branch that serializes the request.The same branch builds the
boto3=Truepayload thatTrainingQueuesubmits to AWS Batch (sagemaker-train/src/sagemaker/train/aws_batch/training_queue.py), so Batch-submitted training jobs carried the same invalid tags; this fixes both.The interactive request deliberately keeps the dicts.
TrainingJob.createannotates itstagsparameter withsagemaker.core.resources.Tag— a resource class that shadows the shape of the same name, sinceresources.pydoesfrom sagemaker.core.shapes import *and then redefinesTag.Job.create, defined earlier in the module, still resolves toshapes.Tag:A
shapes.Taginstance therefore failsTrainingJob.create's validation (Input should be a valid dictionary or instance of Tag … input_type=Tag), while the dicts are revalidated into that model and serialized correctly. That is why this fix is scoped to the serializing branch rather than replacingtags_as_dictswithself.tagseverywhere, which breaks interactive training. The duplicateTagclass looks worth addressing separately.self.tagsis safe to pass here:ModelTrainersetsvalidate_assignment=Trueon anOptional[List[Tag]]field, so it is alwaysNoneor a list of validatedTagmodels.Testing
Added 5 tests to
sagemaker-train/tests/unit/train/test_model_trainer.py: the pipeline request usesKey/Value, theboto3=Truepayload likewise, an untagged trainer adds noTagsmember, the interactive request keeps the dictsTrainingJob.createaccepts and they arrive boto-style on the wire (measured through a mocked boto client, not derived), and both paths emit identical tags.With the fix reverted, exactly the 3 tests that assert boto-style keys fail; the other 2 pin behavior this change deliberately leaves alone.
Beyond the unit tests, I validated the step's entire
Argumentsdict against botocore's realCreateTrainingJobinput shape — the same contract the service parses a pipeline definition against:ArgumentsMissing required parameter in Tags[0]: "Key"Running the reproduction script from the issue now prints
[{"Key": "project", "Value": "pipeline-tag-reproduction"}].The 19 failures are identical with the fix reverted and are environmental in my sandbox, not regressions: 14 require a botocore that describes the SageMaker training-session operations, and 5 call live AWS (
DescribeHubContent).black --check,flake8andpydocstyleare clean on both changed files;pylint --rcfile=.pylintrcreports the same 11 pre-existing findings and the same 9.81/10 score before and after.I was not able to run a live
CreatePipeline/upsertround-trip (no credentials in my environment), so that last confirmation is outstanding.Merge Checklist
Put an
xin the boxes that apply.General
Tests
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.