diff --git a/litellm/llms/anthropic/messages/adapters/handler.py b/litellm/llms/anthropic/messages/adapters/handler.py index 31a4e9d9d73..c654438f841 100644 --- a/litellm/llms/anthropic/messages/adapters/handler.py +++ b/litellm/llms/anthropic/messages/adapters/handler.py @@ -21,6 +21,7 @@ from litellm.litellm_core_utils.tool_fields import ( tools_without_mcp_servers, ) from litellm.llms.anthropic.messages.adapters.transformation import ( + ANTHROPIC_ONLY_REQUEST_KEYS, AnthropicAdapter, ) from litellm.llms.anthropic.messages.context_management import ( @@ -56,8 +57,6 @@ if TYPE_CHECKING: from litellm.router import Router from litellm.types.proxy.auth.user_api_key_auth import UserAPIKeyAuth -# Anthropic-only keys already mapped by the translator; strip on extra_kwargs re-merge. -ANTHROPIC_ONLY_REQUEST_KEYS: Final[frozenset[str]] = frozenset({"output_config", "safeguards"}) _REQUEST_KWARGS: Final = TypeAdapter(Mapping[str, object]) _AnthropicMessages: TypeAlias = "list[dict[str, object]]" diff --git a/litellm/llms/anthropic/messages/adapters/transformation.py b/litellm/llms/anthropic/messages/adapters/transformation.py index afcd94c5b2c..824cb19587f 100644 --- a/litellm/llms/anthropic/messages/adapters/transformation.py +++ b/litellm/llms/anthropic/messages/adapters/transformation.py @@ -215,6 +215,10 @@ from litellm.utils import supports_mid_conversation_system from .streaming_iterator import AnthropicSSEStream, AnthropicStreamWrapper +ANTHROPIC_ONLY_REQUEST_KEYS: Final[frozenset[str]] = frozenset( + {"output_config", "safeguards", "inference_geo", "mcp_servers", "speed"} +) + if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObject from litellm.llms.anthropic.messages.safeguards import SafeguardsEvaluator @@ -1272,7 +1276,7 @@ class LiteLLMAnthropicMessagesAdapter: """Copy through anthropic params that do not require translation.""" translatable_params: Final = self.translatable_anthropic_params() for k, v in anthropic_message_request.items(): - if k not in translatable_params: # pass remaining params as is + if k not in translatable_params and k not in ANTHROPIC_ONLY_REQUEST_KEYS: new_kwargs[k] = v def translate_anthropic_to_openai( diff --git a/tests/integration/translation/messages/params/__init__.py b/tests/integration/translation/messages/params/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/integration/translation/messages/params/test_messages_params_anthropic_only.py b/tests/integration/translation/messages/params/test_messages_params_anthropic_only.py new file mode 100644 index 00000000000..5d384b3cfee --- /dev/null +++ b/tests/integration/translation/messages/params/test_messages_params_anthropic_only.py @@ -0,0 +1,27 @@ +from dataclasses import replace +from typing import Final + +import pytest +from integration._support.client import Gateway +from integration._support.provider import SharedProvider +from integration.translation.case import TranslationTestCase +from integration.translation.messages.bases.azure import GPT_5_4_TEST_CASE +from integration.translation.runner import assert_translation + +GPT_5_4_ANTHROPIC_ONLY_PARAMS_TEST_CASE: Final = replace( + GPT_5_4_TEST_CASE, + scenario="anthropic_only_params_dropped", + litellm_request={ + **GPT_5_4_TEST_CASE.litellm_request, + "inference_geo": "us", + "mcp_servers": [{"type": "url", "url": "https://mcp.example.com/sse", "name": "example"}], + "speed": "fast", + }, +) + +CASES: Final = (GPT_5_4_ANTHROPIC_ONLY_PARAMS_TEST_CASE,) + + +@pytest.mark.parametrize("case", CASES, ids=lambda case: case.id) +def test_messages_params_anthropic_only(case: TranslationTestCase, gateway: Gateway, provider: SharedProvider) -> None: + assert_translation(case, gateway, provider) diff --git a/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py b/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py index cc366dcde4c..523f3e35f0b 100644 --- a/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py +++ b/tests/unit/llms/anthropic/messages/adapters/test_anthropic_messages_adapters_transformation.py @@ -4108,6 +4108,27 @@ def test_translate_anthropic_tool_choice_none(): assert result == "none" +def test_translate_anthropic_to_openai_drops_anthropic_only_params() -> None: + adapter: Final = LiteLLMAnthropicMessagesAdapter() + + new_kwargs, _ = adapter.translate_anthropic_to_openai( + { + "model": "gpt-5.4", + "max_tokens": 64, + "messages": [{"role": "user", "content": "Say hello."}], + "inference_geo": "us", + "mcp_servers": [{"type": "url", "url": "https://mcp.example.com/sse", "name": "example"}], + "speed": "fast", + "top_k": 7, + } + ) + + assert "inference_geo" not in new_kwargs + assert "mcp_servers" not in new_kwargs + assert "speed" not in new_kwargs + assert new_kwargs["top_k"] == 7 + + # --------------------------------------------------------------------------- # PolyfillResult integration tests # ---------------------------------------------------------------------------