Skip to content

fix: make ModelBuilder.deploy(tags=...) a named parameter that reaches every deploy path - #6528

Closed
evakravi wants to merge 1 commit into
aws:masterfrom
evakravi:fix/model-builder-deploy-tags
Closed

evakravi wants to merge 1 commit into
aws:masterfrom
evakravi:fix/model-builder-deploy-tags

Conversation

@evakravi

Copy link
Copy Markdown
Member

Problem

ModelBuilder.deploy() takes tags only through **kwargs, so inspect.signature(ModelBuilder.deploy) does not list it and a typo in the name is a silent no-op. Two deploy paths drop it: the fine-tuned model package path reads only self._tags, and the Nova path reads neither. The accumulated tags also have two shapes in one SDK. add_tags compares tag["Key"], so the {"key": ..., "value": ...} form that the sagemaker.core.resources classes take raises KeyError: 'Key', and a sagemaker.core.shapes.Tag raises TypeError: 'Tag' object is not subscriptable, while the fine-tuned and Nova paths convert the stored tags back to lowercase by hand. The SageMaker agent skills carry a commit whose only purpose is to pass {"Key", "Value"} dicts to dodge the KeyError.

Reproduction on origin/master:

>>> _validate_new_tags([{"key": "a", "value": "1"}], [{"Key": "b", "Value": "2"}])
KeyError: 'Key'
>>> _validate_new_tags([Tag(key="a", value="1")], [{"Key": "b", "Value": "2"}])
TypeError: 'Tag' object is not subscriptable

Solution

deploy() gets a named tags: Optional[Tags] = None parameter. format_tags normalizes every accepted form once (a {key: value} mapping, boto-style dicts, core-style dicts, Tag objects, or a mix) into boto-style dicts, raises TypeError for a tag with no key, and add_tags, tag_exists, _validate_new_tags, and remove_tag_with_key go through it. deploy() merges the caller's tags into the builder before dispatch, so the built-model, inference-component, recommendation, fine-tuned, restricted-package, and Nova paths all tag the endpoint from the same list. One helper, _core_endpoint_tags(), replaces the two hand-written case conversions. build(tags=...) from #6510 uses the same normalization.

Tests

New sagemaker-serve/tests/unit/test_model_builder_deploy_tags.py, 13 tests: format_tags on every representation and its TypeError cases, _validate_new_tags with mixed casing and Tag objects, remove_tag_with_key on core casing, add_tags normalization, the tags parameter in the signature, the merge into the builder before the core path, _deploy_core_endpoint normalizing kwargs tags, the recommendation path receiving the builder's tags, _core_endpoint_tags() casing, the restricted-package endpoint being tagged, and the Nova endpoint merging the builder's tags.

cd sagemaker-serve && python -m pytest tests/unit/test_model_builder_deploy_tags.py -q
13 passed

On origin/master the same file reports 13 failed (KeyError: 'Key' in five tests, the rest on the missing parameter and the dropped tags).

Existing suites on this branch: sagemaker-core tests/unit/test_common_utils.py -k tag: 16 passed. sagemaker-serve test_model_builder_build_tags.py, test_model_builder_deploy.py, test_merged_model_deployment.py, test_model_builder_core.py, test_model_builder_methods.py, test_model_builder_utils_methods.py, test_model_builder_register.py, test_inference_recommendation_mixin.py, test_model_builder_additional.py: 188 passed, 6 skipped, the one failure (test_get_client_translators_with_torch_tensor, No module named 'torch') fails identically on origin/master.

black --line-length 100, flake8, and pydocstyle pass on the changed files.

Merge Checklist

  • I read the contribution guide.
  • The change is backward compatible.
  • The commits use the repository commit-message format.
  • The change creates no S3 or STS client.
  • The change has regression coverage and adds no dependency.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

…s every deploy path

`deploy()` took `tags` only through `**kwargs`, so `inspect.signature` could
not validate it, and two deploy paths dropped it: the fine-tuned model package
path read only `self._tags`, and the Nova path did not read the kwarg at all.
The accumulated tags also had two shapes in one SDK. `add_tags` compared
`tag["Key"]` and raised `KeyError: 'Key'` for the `{"key": ..., "value": ...}`
form that the `sagemaker.core.resources` classes take, and raised `TypeError`
for a `sagemaker.core.shapes.Tag`, while the fine-tuned path converted the
stored tags back to lowercase by hand.

Add `tags` as a named `deploy()` parameter. Normalize every accepted form (a
`{key: value}` mapping, boto-style dicts, core-style dicts, `Tag` objects) once
in `format_tags`, and make `add_tags`, `tag_exists`, `_validate_new_tags`, and
`remove_tag_with_key` go through it. Merge the caller's tags into the builder
before dispatch so the built-model, inference-component, recommendation,
fine-tuned, restricted-package, and Nova paths all tag the endpoint, and
replace the two hand-written case conversions with one helper.

---
X-AI-Prompt: Why does ModelBuilder.deploy drop my tags on a fine-tuned model, and why does add_tags raise KeyError: 'Key'?
X-AI-Tool: claude-code
@evakravi
evakravi deployed to auto-approve October 23, 2026 17:41 — with GitHub Actions Active
@evakravi
evakravi deployed to auto-approve October 23, 2026 17:42 — with GitHub Actions Active
@evakravi
evakravi deployed to auto-approve October 23, 2026 17:44 — with GitHub Actions Active
@evakravi

Copy link
Copy Markdown
Member Author

Superseded by #6533, which combines the four changes into one PR and moves the hub-model metadata accessors onto HubContent itself (a HubContentMixin in sagemaker.core.hub_content) instead of a separate class.

@evakravi evakravi closed this Oct 23, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI code review

This PR makes ModelBuilder.deploy(tags=...) a named parameter, centralizes tag normalization in format_tags/_normalize_tag so all accepted representations (boto dicts, core-style dicts, Tag objects, {key: value} mappings) become boto-style {"Key","Value"} dicts, and threads a merged tag list through every deploy path (built model, IC, recommendation, fine-tuned, restricted-package, Nova) via add_tags/_core_endpoint_tags. It aligns with prior precedent (PR #6179) and adds focused unit tests. The change is sound; I verified callers of format_tags, _validate_new_tags, and remove_tag_with_key in sagemaker-core, the _deploy_core_endpoint flow, imports, and the existing tag tests. Only one minor edge case worth noting.

Reviewed commit 825999b. Automated review; verify before acting.

if isinstance(tags, dict):
if "Key" in tags or "key" in tags:
return [_normalize_tag(tags)]
return [{"Key": str(k), "Value": str(v)} for k, v in tags.items()]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Low: The dict branch now treats any mapping containing a "Key" or "key" entry as a single tag rather than a {tag_key: tag_value} mapping. This correctly disambiguates a single boto/core tag dict, but it silently changes behavior for a legitimate {tag_key: value} mapping that happens to use "Key"/"key" as one of its tag keys: e.g. format_tags({"Key": "prod", "team": "ml"}) now yields a single tag {"Key": "prod", "Value": None} and drops the "team" entry, whereas before it produced two tags. This is an inherent ambiguity of overloading dict, and unlikely in practice, but it is a new silent data-loss path and may be worth a short note in the docstring or a test guarding the expected precedence.

This branch was successfully deployed

1 active deployment
auto-approve — 825999b4 Deployed Oct 23, 2026 by evakravi via wait-for-approval #737
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant