Skip to content

feat(bedrock): forward bedrock_tags to CreateModelInvocationJob for batch jobs - #33733

Merged
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_lit_4162_bedrock_batch_tags
Jul 20, 2026
Merged

feat(bedrock): forward bedrock_tags to CreateModelInvocationJob for batch jobs#33733
mateo-berri merged 4 commits into
litellm_internal_stagingfrom
litellm_lit_4162_bedrock_batch_tags

Conversation

@mateo-berri

@mateo-berri mateo-berri commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Relevant issues

Linear ticket

Resolves LIT-4162

Pre-Submission checklist

Please complete all items before asking a LiteLLM maintainer to review your PR

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to re-request a review after pushing changes)

Delays in PR merge?

If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).

Screenshots / Proof of Fix

All runs captured at c2bd869 (the PR head) against a live local proxy signing with real AWS credentials (dev account 439158074652, us-west-2). Every job below is a real CreateModelInvocationJob accepted by AWS, fed by a 100-record input JSONL uploaded to S3, and each tag claim is verified with aws bedrock list-tags-for-resource against the returned job ARN. The deployments pin us.anthropic.claude-sonnet-4-6 because Bedrock rejects batch for claude-sonnet-5 outright ("Batch inference is not supported for the requested model")

Before, captured at staging commit 214945a (predates this PR's merge) with the same config, live proxy, and real credentials. The customer's original attempt, top-level tags in the request body, dies on the tag-routing field before any Bedrock call:

{
    "error": {
        "message": "2 validation errors for GenericLiteLLMParams\ntags.0\n  Input should be a valid string [type=string_type, input_value={'key': 'application', 'value': 'genai-proxy'}, input_type=dict]\n    For further information visit https://errors.pydantic.dev/2.13/v/string_type\ntags.1\n  Input should be a valid string [type=string_type, input_value={'key': 'team', 'value': 'ml-platform'}, input_type=dict]\n    For further information visit https://errors.pydantic.dev/2.13/v/string_type",
        "type": "internal_server_error",
        "param": "None",
        "code": "500"
    }
}

And bedrock_tags is silently dropped there: the exact request from item 1 below creates real job xe29mg490si5, but list-tags-for-resource on it returns

{"tags": []}

Proxy config (port 58731):

model_list:
  - model_name: bedrock-batch-sonnet
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      aws_batch_role_arn: arn:aws:iam::439158074652:role/litellm-bedrock-batch-role
  - model_name: bedrock-batch-sonnet-clientside
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      aws_batch_role_arn: arn:aws:iam::439158074652:role/litellm-bedrock-batch-role
      configurable_clientside_auth_params: ["bedrock_tags"]
  - model_name: bedrock-batch-sonnet-tagged
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      aws_batch_role_arn: arn:aws:iam::439158074652:role/litellm-bedrock-batch-role
      bedrock_tags:
        - key: cost-center
          value: ml-platform
  - model_name: bedrock-batch-sonnet-tagged-clientside
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-4-6
      aws_batch_role_arn: arn:aws:iam::439158074652:role/litellm-bedrock-batch-role
      bedrock_tags:
        - key: cost-center
          value: ml-platform
      configurable_clientside_auth_params: ["bedrock_tags"]

litellm_settings:
  enable_loadbalancing_on_batch_endpoints: true

general_settings:
  master_key: sk-lit4162-demo
  1. Request-level bedrock_tags, the customer flow with the field renamed from tags, against a deployment that opts in via configurable_clientside_auth_params:
curl -s -X POST http://localhost:58731/v1/batches \
  -H "Authorization: Bearer sk-lit4162-demo" -H "content-type: application/json" \
  -d '{"input_file_id": "s3://litellm-lit4162-batch-439158074652/batch-input.jsonl", "endpoint": "/v1/chat/completions", "completion_window": "24h", "model": "bedrock-batch-sonnet-clientside", "bedrock_tags": [{"key": "application", "value": "genai-proxy"}, {"key": "team", "value": "ml-platform"}]}'
{
    "id": "arn:aws:bedrock:us-west-2:439158074652:model-invocation-job/79rvz20ap4uq",
    "completion_window": "24h",
    "endpoint": "/v1/chat/completions",
    "input_file_id": "s3://litellm-lit4162-batch-439158074652/batch-input.jsonl",
    "object": "batch",
    "status": "validating"
}

The tags are on the real AWS resource:

aws bedrock list-tags-for-resource --resource-arn arn:aws:bedrock:us-west-2:439158074652:model-invocation-job/79rvz20ap4uq --region us-west-2
{
    "tags": [
        {"key": "application", "value": "genai-proxy"},
        {"key": "team", "value": "ml-platform"}
    ]
}

And the job is retrievable through the proxy like any OpenAI batch:

curl -s "http://localhost:58731/v1/batches/arn%3Aaws%3Abedrock%3Aus-west-2%3A439158074652%3Amodel-invocation-job%2F79rvz20ap4uq?provider=bedrock" \
  -H "Authorization: Bearer sk-lit4162-demo"
{"id": "arn:aws:bedrock:us-west-2:439158074652:model-invocation-job/79rvz20ap4uq", "status": "validating", "object": "batch"}
  1. Malformed tags return a clear error instead of a raw Pydantic traceback. Same curl with "bedrock_tags": ["application=genai-proxy"]:
{"error":{"message":"Invalid 'bedrock_tags' value. Expected a list of {'key': <str>, 'value': <str>} dicts, e.g. [{'key': 'team', 'value': 'genai'}]. Got: ['application=genai-proxy']","type":"internal_server_error","param":"None","code":"500"}}
  1. Deployment-level tags from proxy config, no request-body field, no opt-in needed. The same curl against bedrock-batch-sonnet-tagged without bedrock_tags creates job 817c23imuul0, and list-tags-for-resource on it returns:
{"tags": [{"key": "cost-center", "value": "ml-platform"}]}
  1. Request-level override of config tags on a deployment that opts in. The same curl against bedrock-batch-sonnet-tagged-clientside with bedrock_tags: [{"key": "cost-center", "value": "override-team"}] creates job dlm10mow7vf7, and list-tags-for-resource on it returns:
{"tags": [{"key": "cost-center", "value": "override-team"}]}
  1. Security gate (the Veria review finding): request-body bedrock_tags against any deployment without the opt-in is rejected before any AWS call. Both the plain request against bedrock-batch-sonnet and the override attempt against bedrock-batch-sonnet-tagged return:
{"error":{"message":"Authentication Error, Rejected Request: bedrock_tags is not allowed in request body. Clientside passthrough requires explicit admin opt-in via either `general_settings.allow_client_side_credentials = true` (proxy-wide) or `configurable_clientside_auth_params` on the deployment in your proxy config.yaml. Relevant Issue: https://huntr.com/bounties/4001e1a2-7b7a-4776-a3ae-e6692ec3d997","type":"auth_error","param":"None","code":"401"}}

Type

🆕 New Feature

Changes

A customer (Pylon #5645) runs under an SCP that denies Bedrock batch jobs without resource tags, and LiteLLM had no way to pass any: BedrockBatchesConfig.transform_create_batch_request never populated the tags field of CreateModelInvocationJob. Sending top-level tags in the request body can never work for this because GenericLiteLLMParams.tags is the LiteLLM tag-routing field typed Optional[List[str]], so Bedrock's dict-shaped tags fail validation with a 500. The new parameter is therefore named bedrock_tags

transform_create_batch_request now reads bedrock_tags from litellm_params (with an optional_params fallback, same precedence as aws_batch_role_arn), validates it strictly as a list of {"key": str, "value": str} via a Pydantic TypeAdapter raising a clear ValueError on bad shapes, and sets it on the signed request. BedrockCreateBatchRequest.tags is tightened from Optional[List[dict]] to Optional[List[BedrockTag]]. Since GenericLiteLLMParams allows extras and the router merges request kwargs over deployment litellm_params, the one key works both per request and per deployment in proxy config, with the request value winning

Nine regression tests added to tests/test_litellm/llms/bedrock/batches/test_transformation.py: tags forwarded from litellm_params and from optional_params, the tags key omitted entirely when bedrock_tags is absent, and six malformed shapes rejected before signing. Eight of the nine fail on the parent commit

A router-level test in tests/test_litellm/test_router.py locks the precedence claim end-to-end: Router.acreate_batch with bedrock_tags on the deployment applies the config tags to the signed CreateModelInvocationJob body, and the same call with request-level bedrock_tags overrides them. Mutating the router merge to let deployment litellm_params win over request kwargs fails the test

A Veria review finding pointed out that request-supplied bedrock_tags let any authenticated caller stamp arbitrary ownership or cost-allocation labels on jobs created under the proxy's AWS identity. bedrock_tags is now in _BANNED_REQUEST_BODY_PARAMS, the same gate that covers aws_bedrock_project_id, so per-request tags require either general_settings.allow_client_side_credentials: true proxy-wide or configurable_clientside_auth_params: ["bedrock_tags"] on the deployment; deployment-level bedrock_tags keep working with no opt-in. The same commit stops an explicit empty bedrock_tags list in litellm_params from falling through to the request value (a Greptile note). Four tests in tests/test_litellm/proxy/auth/test_auth_utils.py cover the rejection, both opt-ins, and that an opt-in for a different param does not open bedrock_tags; one more transformation test locks the empty-list behavior

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds bedrock_tags forwarding to Bedrock's CreateModelInvocationJob API, unblocking customers operating under SCPs that require resource tags on batch jobs. It also gates request-body bedrock_tags behind the existing admin opt-in mechanism to prevent callers from forging ownership or cost-allocation labels under the proxy's AWS identity.

  • Tag forwarding: transform_create_batch_request reads bedrock_tags from litellm_params (config-level, wins) then falls back to optional_params (request-level), with an is not None guard so an explicit empty list in config stops the fallthrough. Both sources are validated by a strict Pydantic TypeAdapter[list[BedrockTag]] before reaching the AWS signing call.
  • Security gate: bedrock_tags is added to _BANNED_REQUEST_BODY_PARAMS, matching the pattern already used for aws_bedrock_project_id; proxy-wide or per-deployment opt-in is required for request-body tags, while deployment-level config tags need no opt-in.
  • Test coverage: Nine transformation tests, four auth-gate tests, and one end-to-end router test covering tag precedence, malformed input rejection, and both opt-in paths.

Confidence Score: 5/5

Safe to merge — the change is purely additive, the security gate mirrors established patterns, and the precedence logic is backed by targeted tests.

The transformation logic is straightforward, the is not None guard correctly handles the empty-list edge case flagged in the previous review, the auth gate correctly reuses the existing _BANNED_REQUEST_BODY_PARAMS / _check_banned_params infrastructure, and the 14 new tests cover all documented scenarios including malformed inputs and both opt-in paths.

No files require special attention.

Important Files Changed

Filename Overview
litellm/llms/bedrock/batches/transformation.py Adds bedrock_tags forwarding to CreateModelInvocationJob: reads from litellm_params first (config-level) with is not None guard to prevent empty-list fallthrough, then falls back to optional_params; validates both sources with a strict Pydantic TypeAdapter before signing.
litellm/proxy/auth/auth_utils.py Adds bedrock_tags to _BANNED_REQUEST_BODY_PARAMS, requiring admin opt-in (allow_client_side_credentials or configurable_clientside_auth_params) before a caller-supplied value can reach the AWS signing path; mirrors the existing pattern for aws_bedrock_project_id.
litellm/types/llms/bedrock.py Introduces BedrockTag TypedDict with key/value string fields and tightens BedrockCreateBatchRequest.tags from Optional[List[dict]] to Optional[List[BedrockTag]].
tests/test_litellm/llms/bedrock/batches/test_transformation.py Adds nine new tests: tags forwarded from litellm_params and optional_params, empty-list-prevents-fallthrough, tags-absent-omits-key, and six malformed-shape rejections with assert_not_called() on sign_aws_request.
tests/test_litellm/proxy/auth/test_auth_utils.py Adds four auth-gate tests: rejection without opt-in, proxy-wide opt-in allows tags, per-deployment opt-in allows tags, and per-deployment opt-in for a different param does not open bedrock_tags.
tests/test_litellm/test_router.py Adds an async router-level test verifying deployment-level tags are applied by default and request-level tags (passed as bedrock_tags kwarg to acreate_batch) override them, locking the end-to-end precedence claim.

Reviews (2): Last reviewed commit: "fix(proxy): require admin opt-in for req..." | Re-trigger Greptile

Comment thread litellm/llms/bedrock/batches/transformation.py Outdated
@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread litellm/llms/bedrock/batches/transformation.py Outdated
@veria-ai

veria-ai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

PR overview

All previously flagged issues have been addressed. No open security concerns remain on this pull request.

Security review

No open security issues remain on this pull request.

Fixed/addressed: 1 · PR risk: 0/10

@codspeed-hq

codspeed-hq Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 31 untouched benchmarks


Comparing litellm_lit_4162_bedrock_batch_tags (c2bd869) with litellm_internal_staging (214945a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on litellm_internal_staging (c2bd869) during the generation of this report, so 214945a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

…itellm_lit_4162_bedrock_batch_tags

# Conflicts:
#	tests/test_litellm/test_router.py
Caller-supplied bedrock_tags land as AWS resource tags under the proxy's
AWS identity, letting an authenticated caller forge ownership or
cost-allocation labels. Add bedrock_tags to _BANNED_REQUEST_BODY_PARAMS
so per-request tags need general_settings.allow_client_side_credentials
or configurable_clientside_auth_params on the deployment, matching the
aws_bedrock_project_id precedent. Deployment-level bedrock_tags in
litellm_params are unaffected.

Also stop an explicit empty bedrock_tags list in litellm_params from
falling through to optional_params
@mateo-berri

Copy link
Copy Markdown
Contributor Author

@greptileai

@mateo-berri
mateo-berri enabled auto-merge July 20, 2026 22:09
@mateo-berri
mateo-berri merged commit 3819ee5 into litellm_internal_staging Jul 20, 2026
79 of 80 checks passed
@mateo-berri
mateo-berri deleted the litellm_lit_4162_bedrock_batch_tags branch July 20, 2026 22:12
yuneng-berri added a commit that referenced this pull request Jul 22, 2026
…-proxy-extras to 0.4.79.post1 (#34215)

* feat(spend): track prompt compression saved tokens in daily spend aggregates (#33810)

* feat(spend): track prompt compression saved tokens in daily spend aggregates

Native compression interception now records tokens_before/after/saved into the
request litellm_metadata so savings land in the SpendLog metadata JSON under a
typed compression_savings key. A single normalizer
(extract_compression_saved_tokens) sums that key with Headroom guardrail
tokens_saved; the two writers are disjoint and run at different stages, so
summing never double-counts. The spend-log redactor now preserves purely
numeric compression stats inside guardrail_response so Headroom savings
survive the store_prompts_in_spend_logs=false default. compression_saved_tokens
is threaded through BaseDailySpendTransaction, queue aggregation, the daily
upsert blocks, a new BigInt column on all six daily spend tables, and the
daily activity read path (SpendMetrics, DailySpendMetadata, raw-SQL rollups)

* fix(spend): normalize legacy guardrail shapes and float token stats in compression savings reader

* feat(spend): aggregate compression and prompt caching dollar savings in daily rollups

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(spend): update daily spend aggregation fixtures for savings columns

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* feat(ui): add Cost Optimization dashboard page

New left-nav Cost Optimization page under Observability that surfaces money saved by prompt compression and prompt caching. It reads the daily activity rollup (userDailyActivityCall / get_daily_activity) and never scans SpendLogs, so it stays fast at 1M+ rows.

Renders a Total saved card, per-driver Compression and Prompt caching cards, a savings-over-time area chart, and a savings-by-driver donut, all aggregated in memory from the per-day metrics.compression_savings_spend and metrics.prompt_caching_savings_spend fields.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 3f3295b)

* Merge pull request #33733 from BerriAI/litellm_lit_4162_bedrock_batch_tags

feat(bedrock): forward bedrock_tags to CreateModelInvocationJob for batch jobs

(cherry picked from commit 3819ee5)

* bump: version 0.4.79 → 0.4.79.post1

---------

Co-authored-by: tin-berri <tin@berri.ai>
Co-authored-by: Krrish Dholakia <krrishdholakia@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Mateo Wang <277851410+mateo-berri@users.noreply.github.com>
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.

2 participants