Skip to content

evaluator_model was checked against a hardcoded _ALLOWED_EVALUATOR_MO… - #6217

Open
mohamedzeidan2021 wants to merge 1 commit into
aws:masterfrom
mohamedzeidan2021:llaj-hardcoding
Open

evaluator_model was checked against a hardcoded _ALLOWED_EVALUATOR_MO…#6217
mohamedzeidan2021 wants to merge 1 commit into
aws:masterfrom
mohamedzeidan2021:llaj-hardcoding

Conversation

@mohamedzeidan2021

Copy link
Copy Markdown
Collaborator

…DELS

LLMAsJudgeEvaluator.evaluator_model was validated against a hardcoded _ALLOWED_EVALUATOR_MODELS dict (model → regions) in sagemaker/train/constants.py. This PR replaces that with two-step validation against authoritative, service-maintained sources, and removes the dict.

Problem

  1. Maintenance toil. Every Bedrock judge-model add/deprecation required hand-editing the dict and cutting an SDK release — and the same list is triplicated across the SDK, Studio UI, and SageMaker Agent Skills.
  2. Stale list → deep runtime failures. When a judge model reaches end of life the dict still lists it, so it passes client-side validation, the eval job spins up, and only the in-container Bedrock CreateEvaluationJob call fails ("model version has reached end of life"). Compute is wasted and the error surfaces deep inside a running job instead of failing fast.

This is real today: the supported list still advertises claude-3-5-sonnet-20240620, claude-3-5-haiku, claude-3-5-sonnet-v2, and claude-3-7-sonnet, all of which return ResourceNotFoundException from Bedrock in us-west-2.

Solution — two-step validation

Step 1 — construction (is it a judge-capable model?): fetch the service-maintained list at s3://jumpstart-cache-prod-/fmhMetadata/supported-llmaj-judge-models.json (source of truth for supported judge models) and fail fast if evaluator_model isn't in it.

Step 2 — evaluate() (is it still in service?): the supported list is a superset that can still include end-of-life models, so we call bedrock:GetFoundationModel and fail fast if the model is unavailable in the region or past its endOfLifeTime. The lookup is gated on the caller's IAM permission via a new non-raising caller_can_perform() helper that reuses the existing iam:SimulatePrincipalPolicy caller-check pattern (verify_evaluation_caller_permissions).

Graceful degradation everywhere: we never block on "can't tell." If a source can't be read (missing permission, unreadable list, transient error), the SDK logs an actionable warning with a link to the supported models and continues.

User-facing messaging

Fail-fast errors (block construction / submission):

Warnings (degrade, do not block):

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

…DELS

dict (model -> regions) in sagemaker/train/constants.py. That list is
triplicated across clients and goes stale: when a judge model reaches end
of life it still passes client-side validation, so the eval job spins up
and only fails deep inside the in-container Bedrock CreateEvaluationJob
call, wasting compute and surfacing a poor error.

Replace it with two-step validation against authoritative sources:

- Construction: fetch the service-maintained supported-judge-models list at
  s3://jumpstart-cache-prod-<region>/fmhMetadata/supported-llmaj-judge-models.json
  and fail fast if evaluator_model is not a supported judge model.
- evaluate(): call bedrock:GetFoundationModel and fail fast if the model is
  unavailable in the region or past its endOfLifeTime. The lookup is gated on
  the caller's IAM permission via a new non-raising caller_can_perform()
  helper that mirrors the existing SimulatePrincipalPolicy caller-check
  pattern (verify_evaluation_caller_permissions).

Both steps degrade gracefully instead of blocking: if a source can't be read
(missing bedrock:GetFoundationModel permission, unreadable list, or a
transient error) the SDK logs an actionable warning with a link to the
supported models and continues.

- Remove _ALLOWED_EVALUATOR_MODELS from sagemaker/train/constants.py
- Add caller_can_perform() to sagemaker/core/helper/iam_role_resolver.py
- Add unit tests for both validation steps and caller_can_perform
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Claude finished @mohamedzeidan2021's task in 3m 13s —— View job


Review: replace hardcoded _ALLOWED_EVALUATOR_MODELS with service-backed validation

Thanks for tackling the stale-list problem — the two-step design (construction = "is it a judge model", evaluate() = "is it still in service") is sound, the graceful-degradation-everywhere policy is applied consistently, the triplicated dict removal is clean, and the new paths are well tested. A few things worth a look before merge.

Note: the inline-comment tool isn't available to me in this run, so findings are below with file:line references instead of anchored review comments.


1. (medium) The bedrock:GetFoundationModel permission gate can silently skip the EOL check for callers who do hold the permission

sagemaker-train/.../llm_as_judge_evaluator.py:345

allowed = caller_can_perform(["bedrock:GetFoundationModel"], self.sagemaker_session)

bedrock:GetFoundationModel is a resource-scoped action, and caller_can_perform simulates it without ResourceArns — its own docstring warns that "resource-scoped actions can come back implicitDeny even for a caller who holds them" (iam_role_resolver.py:772-775). So any caller whose grant is scoped to specific foundation-model ARNs (rather than * / AmazonBedrockFullAccess) gets allowed is False → the SDK warns and skips the exact EOL check this PR adds, silently defeating step 2 for the security-conscious users most likely to scope their policies.

Meanwhile, a genuine lack of permission is already handled downstream: get_foundation_model would raise AccessDeniedException, which falls through the ResourceNotFoundException/ValidationException branch into the warn-and-continue path at lines 388-397. So the gate mostly buys false negatives plus 3 extra STS/IAM calls (GetCallerIdentity, GetRole, SimulatePrincipalPolicy) per evaluate().

Suggestion: drop the pre-gate and call get_foundation_model directly, mapping AccessDenied* to the "couldn't confirm permission / can't verify" warning. Same UX, no false negatives, fewer API calls. If you keep the gate, simulate with the proper foundation-model ResourceArns so scoped policies resolve correctly.

2. (medium) Construction now performs synchronous S3 network I/O inside a pydantic validator

sagemaker-train/.../llm_as_judge_evaluator.py:292

_validate_evaluator_model does an S3 GetObject against jumpstart-cache-prod-<region> on every LLMAsJudgeEvaluator(...) construction. This changes construction from a cheap in-memory op into a network- and credential-dependent one:

  • adds latency to every instantiation;
  • other unit tests that construct the evaluator without patching S3Downloader.read_file will now attempt a real network call each time (they'll degrade with a warning, but slow/flaky in CI);
  • step-1 validation only runs when sagemaker_session is explicitly passed — if the session is resolved lazily (e.g. via TrainDefaults.get_sagemaker_session), values.get('sagemaker_session') is None and the check always degrades silently.

Consider caching the per-region list (module-level or lru) and/or documenting that construction now touches S3.

3. (low) Step-1 relies on an assumed external JSON schema, failing open if it's wrong

llm_as_judge_evaluator.py:247-262

_fetch_supported_judge_model_ids assumes {"supported_judge_models": [{"model_id": "..."}]}. If the real file's shape ever differs, the parse yields None → warn → continue, so the SDK silently stops validating any model while appearing to. That's the safe failure direction, but given the whole PR is motivated by a stale/wrong list, a brief note (or a log at debug distinguishing "unreadable" from "unexpected shape") would help future debugging.

4. (nit) Duplicated degradation block

llm_as_judge_evaluator.py:388-397 and 398-407 are byte-for-byte identical warning bodies (except ClientError non-matching branch vs except Exception). Both are reachable, just collapsible. Harmless.


Nothing here is a hard blocker — the degradation policy means everything fails open. Finding #1 is the one I'd most want addressed, since it can quietly neutralize the new EOL guard for scoped-permission callers. Tests look thorough for the paths that exist; the main untested gap is that evaluate() actually invokes _check_evaluator_model_lifecycle, but that's minor.
· branch llaj-hardcoding

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