diff --git a/litellm/llms/bedrock/claude_platform/common_utils.py b/litellm/llms/bedrock/claude_platform/common_utils.py index af340a0efbe..9dc57119358 100644 --- a/litellm/llms/bedrock/claude_platform/common_utils.py +++ b/litellm/llms/bedrock/claude_platform/common_utils.py @@ -14,7 +14,7 @@ CLAUDE_PLATFORM_BEDROCK_ROUTE = "claude_platform/" # must not be forwarded in the Messages API request body (together with any # key prefixed "aws_") — the API rejects unknown fields with # "Extra inputs are not permitted". -CLAUDE_PLATFORM_NON_REQUEST_PARAMS = { +CLAUDE_PLATFORM_ON_AWS_NON_REQUEST_PARAMS = { "workspace_id", "anthropic_workspace_id", "anthropic-workspace-id", @@ -25,12 +25,15 @@ CLAUDE_PLATFORM_NON_REQUEST_PARAMS = { # endpoint, which rejects them with "Extra inputs are not permitted". Unlike # the auth params above these carry user intent, so dropping them is logged at # WARNING — the request succeeds but the requested feature is not applied. -CLAUDE_PLATFORM_UNSUPPORTED_REQUEST_PARAMS = { +CLAUDE_PLATFORM_ON_AWS_UNSUPPORTED_REQUEST_PARAMS = { "context_management", } -def filter_claude_platform_request_body(params: dict) -> dict: +def filter_claude_platform_request_body( + params: dict, + unsupported_override: Optional[frozenset[str]] = None, +) -> dict: """Return a copy of ``params`` with fields the Claude Platform on AWS endpoint rejects removed. @@ -41,15 +44,23 @@ def filter_claude_platform_request_body(params: dict) -> dict: WARNING, since those reflect user intent that will not be applied on this route. + ``unsupported_override``, when provided, replaces the default + CLAUDE_PLATFORM_ON_AWS_UNSUPPORTED_REQUEST_PARAMS set. Pass an empty frozenset + to disable unsupported-param filtering entirely (e.g. when the AWS + endpoint adds support before a litellm release). + Filters a copy so callers' ``sign_request`` still sees ``aws_region_name``. """ - dropped_unsupported = [ - k for k in params if k in CLAUDE_PLATFORM_UNSUPPORTED_REQUEST_PARAMS - ] + unsupported = ( + unsupported_override + if unsupported_override is not None + else CLAUDE_PLATFORM_ON_AWS_UNSUPPORTED_REQUEST_PARAMS + ) + dropped_unsupported = [k for k in params if k in unsupported] if dropped_unsupported: verbose_logger.warning( "bedrock/claude_platform: dropping unsupported Messages API " - "param(s) %s from the request body — the Claude Platform on AWS " + "param(s) %s from the request body - the Claude Platform on AWS " "(aws-external-anthropic) endpoint does not support them and " "rejects unknown fields. The request will proceed without them.", dropped_unsupported, @@ -57,12 +68,29 @@ def filter_claude_platform_request_body(params: dict) -> dict: return { k: v for k, v in params.items() - if k not in CLAUDE_PLATFORM_NON_REQUEST_PARAMS - and k not in CLAUDE_PLATFORM_UNSUPPORTED_REQUEST_PARAMS + if k not in CLAUDE_PLATFORM_ON_AWS_NON_REQUEST_PARAMS + and k not in unsupported and not k.startswith("aws_") } +def _resolve_unsupported_override( + litellm_params: dict, +) -> Optional[frozenset[str]]: + """Read ``claude_platform_unsupported_params`` from litellm_params. + + Returns None (use default set) when the key is absent. Returns a + frozenset when present, allowing operators to override or clear the + unsupported-param list via proxy config without a code change. + """ + raw = litellm_params.get("claude_platform_unsupported_params") + if raw is None: + return None + if isinstance(raw, (list, set, frozenset, tuple)): + return frozenset(raw) + return None + + def strip_claude_platform_route(model: str) -> str: if model.startswith(CLAUDE_PLATFORM_BEDROCK_ROUTE): return model.replace(CLAUDE_PLATFORM_BEDROCK_ROUTE, "", 1) diff --git a/litellm/llms/bedrock/claude_platform/messages_transformation.py b/litellm/llms/bedrock/claude_platform/messages_transformation.py index a4f315af8f6..5b9f7c2c7c2 100644 --- a/litellm/llms/bedrock/claude_platform/messages_transformation.py +++ b/litellm/llms/bedrock/claude_platform/messages_transformation.py @@ -10,6 +10,7 @@ from litellm.types.router import GenericLiteLLMParams from .common_utils import ( BedrockClaudePlatformMixin, + _resolve_unsupported_override, filter_claude_platform_request_body, strip_claude_platform_route, ) @@ -66,13 +67,11 @@ class BedrockClaudePlatformMessagesConfig( litellm_params: GenericLiteLLMParams, headers: dict, ) -> Dict: - # Strip auth/routing config (workspace_id, aws_*) and Messages API - # fields the AWS endpoint does not support (e.g. context_management) - # from the body — the API rejects unknown fields with "Extra inputs - # are not permitted". + unsupported_override = _resolve_unsupported_override(litellm_params) anthropic_messages_optional_request_params = ( filter_claude_platform_request_body( - anthropic_messages_optional_request_params + anthropic_messages_optional_request_params, + unsupported_override=unsupported_override, ) ) return super().transform_anthropic_messages_request( diff --git a/litellm/llms/bedrock/claude_platform/transformation.py b/litellm/llms/bedrock/claude_platform/transformation.py index e34776755c8..1b2e747db0a 100644 --- a/litellm/llms/bedrock/claude_platform/transformation.py +++ b/litellm/llms/bedrock/claude_platform/transformation.py @@ -7,6 +7,7 @@ from litellm.types.llms.openai import AllMessageValues from .common_utils import ( BedrockClaudePlatformMixin, + _resolve_unsupported_override, filter_claude_platform_request_body, ) @@ -93,12 +94,10 @@ class BedrockClaudePlatformConfig(BedrockClaudePlatformMixin, AnthropicConfig): litellm_params: dict, headers: dict, ) -> dict: - # Strip auth/routing config (workspace_id, aws_*) and Messages API - # fields the AWS endpoint does not support (e.g. context_management) - # from the body — the API rejects unknown fields with "Extra inputs - # are not permitted". Filters a copy so sign_request still sees - # aws_region_name. - optional_params = filter_claude_platform_request_body(optional_params) + unsupported_override = _resolve_unsupported_override(litellm_params) + optional_params = filter_claude_platform_request_body( + optional_params, unsupported_override=unsupported_override + ) return super().transform_request( model=model, messages=messages, diff --git a/tests/test_litellm/llms/bedrock/test_claude_platform_provider.py b/tests/test_litellm/llms/bedrock/test_claude_platform_provider.py index 111aab961f1..800f0f8f535 100644 --- a/tests/test_litellm/llms/bedrock/test_claude_platform_provider.py +++ b/tests/test_litellm/llms/bedrock/test_claude_platform_provider.py @@ -361,14 +361,15 @@ def test_claude_platform_messages_strips_auth_params_from_request_body(): ) assert config is not None + input_params = { + "workspace_id": "wrkspc_test", + "aws_region_name": "us-west-2", + "max_tokens": 10, + } request_body = config.transform_anthropic_messages_request( model="claude_platform/claude-sonnet-4-6", messages=[{"role": "user", "content": "hello"}], - anthropic_messages_optional_request_params={ - "workspace_id": "wrkspc_test", - "aws_region_name": "us-west-2", - "max_tokens": 10, - }, + anthropic_messages_optional_request_params=input_params, litellm_params={}, headers={}, ) @@ -376,6 +377,8 @@ def test_claude_platform_messages_strips_auth_params_from_request_body(): assert "workspace_id" not in request_body assert "aws_region_name" not in request_body assert request_body["max_tokens"] == 10 + assert input_params["aws_region_name"] == "us-west-2" + assert input_params["workspace_id"] == "wrkspc_test" def test_claude_platform_strips_unsupported_context_management_param(caplog): @@ -433,19 +436,48 @@ def test_claude_platform_messages_strips_unsupported_context_management_param(): ) assert config is not None + input_params = { + "context_management": {"edits": [{"type": "clear_tool_uses_20250919"}]}, + "max_tokens": 10, + } request_body = config.transform_anthropic_messages_request( model="claude_platform/claude-sonnet-4-6", messages=[{"role": "user", "content": "hello"}], - anthropic_messages_optional_request_params={ - "context_management": {"edits": [{"type": "clear_tool_uses_20250919"}]}, - "max_tokens": 10, - }, + anthropic_messages_optional_request_params=input_params, litellm_params={}, headers={}, ) assert "context_management" not in request_body assert request_body["max_tokens"] == 10 + assert "context_management" in input_params + + +def test_claude_platform_unsupported_override_allows_context_management(): + """ + Operators can pass claude_platform_unsupported_params=[] in litellm_params + to stop filtering context_management once the AWS endpoint supports it. + """ + from litellm.llms.bedrock.claude_platform.transformation import ( + BedrockClaudePlatformConfig, + ) + + config = BedrockClaudePlatformConfig() + optional_params = { + "context_management": {"edits": [{"type": "clear_tool_uses_20250919"}]}, + "max_tokens": 10, + } + + request_body = config.transform_request( + model="claude-sonnet-4-6", + messages=[{"role": "user", "content": "hello"}], + optional_params=optional_params, + litellm_params={"claude_platform_unsupported_params": []}, + headers={}, + ) + + assert request_body["max_tokens"] == 10 + assert "context_management" in request_body def test_chat_completion_claude_platform_sigv4_body_has_no_auth_params():