From 3e651127ae3b70fdb7c67ae279ae622ab82f9e7e Mon Sep 17 00:00:00 2001 From: hx <1367557521@qq.com> Date: Sun, 27 Sep 2026 18:05:43 +0800 Subject: [PATCH] fix(anthropic): normalize [1m] models before capability lookups Review follow-ups for the context-1m change The chat transform now treats a [1m] request exactly like its base model, so model-info lookups (default max_tokens, mid-conversation system support, thinking and speed handling) no longer miss on the suffixed name. Before this, anthropic/claude-sonnet-4-6[1m] silently fell back to a 4096 max_tokens default Detection now reads only the model string. The _original_model stash was never populated once proxy-level stripping was dropped, so those lookups and the duplicate injection in update_headers_with_optional_anthropic_beta are removed. The beta value lives in ANTHROPIC_BETA_HEADER_VALUES Tests move to the mirrored unit test files and cover litellm.acompletion and Router.aanthropic_messages end to end with the HTTP layer mocked --- litellm/llms/anthropic/chat/transformation.py | 41 ++--- litellm/llms/anthropic/common_utils.py | 49 +----- .../pass_through/messages/transformation.py | 20 +-- litellm/types/llms/anthropic.py | 1 + .../test_anthropic_chat_transformation.py | 53 +++++++ .../test_anthropic_messages_context_1m.py | 57 +++++++ .../anthropic/test_anthropic_common_utils.py | 39 ++++- .../llms/anthropic/test_context_1m_header.py | 144 ------------------ 8 files changed, 177 insertions(+), 227 deletions(-) create mode 100644 tests/unit/llms/anthropic/pass_through/messages/test_anthropic_messages_context_1m.py delete mode 100644 tests/unit/llms/anthropic/test_context_1m_header.py diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 08ddaaf0e14..ad07be45a22 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -121,8 +121,8 @@ from ..common_utils import ( REFUSAL_STOP_FIELDS, AnthropicError, AnthropicModelInfo, - context_1m_requested, eager_input_streaming_flag, + has_context_1m_suffix, process_anthropic_headers, refusal_provider_specific_fields, requires_native_compaction_beta, @@ -2138,17 +2138,6 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ) return tools - def _maybe_add_context_1m_beta( - self, - headers: dict, - *, - model: str, - optional_params: dict, - litellm_params: Mapping[str, object] | None, - ) -> None: - if context_1m_requested(model=model, optional_params=optional_params, litellm_params=litellm_params): - self._ensure_beta_header(headers, "context-1m-2025-08-07") - def _ensure_beta_header(self, headers: dict[str, str], beta_value: str) -> None: """ Ensure a beta header value is present in the anthropic-beta header. @@ -2207,12 +2196,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ) def update_headers_with_optional_anthropic_beta( - self, - headers: dict, - optional_params: dict, - messages: Sequence[object] = (), - litellm_params: Mapping[str, object] | None = None, - model: str = "", + self, headers: dict, optional_params: dict, messages: Sequence[object] = () ) -> dict: """Update headers with optional anthropic beta.""" @@ -2225,10 +2209,6 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): if requires_native_compaction_beta(self._resolved_provider, optional_params, messages): self._ensure_beta_header(headers, ANTHROPIC_BETA_HEADER_VALUES.COMPACT_2026_09_04.value) - self._maybe_add_context_1m_beta( - headers, model=model, optional_params=optional_params, litellm_params=litellm_params - ) - _tools: Final = optional_params.get("tools", []) for tool in _tools: if tool.get("type", None) and tool.get("type").startswith(ANTHROPIC_HOSTED_TOOLS.WEB_FETCH.value): @@ -2283,6 +2263,15 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): """ Translate messages to anthropic format. """ + if has_context_1m_suffix(model): + return AnthropicConfig.transform_request( + self, + model=strip_context_1m_suffix(model), + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + headers=headers, + ) ## VALIDATE REQUEST """Anthropic requires ``tools`` when messages include tool blocks; LiteLLM injects a dummy tool if omitted (no ``modify_params`` needed).""" from litellm.litellm_core_utils.prompt_templates.factory import ( @@ -2384,11 +2373,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): ) # don't use verbose_logger.exception, if exception is raised self.update_headers_with_optional_anthropic_beta( - headers=headers, - optional_params=optional_params, - messages=anthropic_messages, - litellm_params=litellm_params, - model=model, + headers=headers, optional_params=optional_params, messages=anthropic_messages ) ## Auto-strip advisor blocks from history if advisor tool is absent. @@ -2476,7 +2461,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): request_params.pop("output_format") data: Final = { - "model": strip_context_1m_suffix(model), + "model": model, "messages": anthropic_messages, **optional_params, **({} if marked_tools is None else {"tools": list(marked_tools)}), diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index b9aef6a9d33..e36658825b1 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -45,6 +45,7 @@ from litellm.llms.anthropic.wif import ( from litellm.llms.base_llm.base_utils import BaseLLMModelInfo, BaseTokenCounter from litellm.llms.base_llm.chat.transformation import BaseLLMException from litellm.types.llms.anthropic import ( + ANTHROPIC_BETA_HEADER_VALUES, ANTHROPIC_HOSTED_TOOLS, ANTHROPIC_MID_CONVERSATION_OUTPUT_CONFIG_BETA_HEADER, ANTHROPIC_MID_CONVERSATION_TOOL_CHANGES_BETA_HEADER, @@ -115,43 +116,14 @@ _DOTTED_VERSION_RE: Final = re.compile(r"(\d)\.(\d)") _CONTEXT_1M_SUFFIX: Final = re.compile(r"\[1m\]$", flags=re.IGNORECASE) -def model_has_context_1m_suffix(model: object) -> bool: - return isinstance(model, str) and _CONTEXT_1M_SUFFIX.search(model) is not None +def has_context_1m_suffix(model: str) -> bool: + return _CONTEXT_1M_SUFFIX.search(model) is not None def strip_context_1m_suffix(model: str) -> str: return _CONTEXT_1M_SUFFIX.sub("", model) -def _original_model_from(params: object) -> object: - if not isinstance(params, Mapping): - return None - return params.get("_original_model") - - -def context_1m_requested( - *, - model: object = "", - optional_params: object = None, - litellm_params: object = None, -) -> bool: - return any( - model_has_context_1m_suffix(candidate) - for candidate in ( - model, - _original_model_from(optional_params), - _original_model_from(litellm_params), - ) - ) - - -_CONTEXT_1M_BETA: Final = "context-1m-2025-08-07" - - -def context_1m_beta_values(supported: bool) -> frozenset[str]: - return frozenset((_CONTEXT_1M_BETA,)) if supported else frozenset() - - _CLAUDE_CODE_BILLING_HEADER_PREFIX: Final = "x-anthropic-billing-header:" OBJECT_MAPPING_ADAPTER: Final = TypeAdapter(dict[object, object]) OBJECT_LIST_ADAPTER: Final = TypeAdapter(list[object]) @@ -1280,9 +1252,11 @@ class AnthropicModelInfo(BaseLLMModelInfo): is_thinking_display_updates_used: bool = False, is_mid_conversation_tool_change_used: bool = False, wif_minted: bool = False, - context_1m_supported: bool = False, + context_1m_requested: bool = False, ) -> dict: - betas: Final = set(context_1m_beta_values(context_1m_supported)) + betas: Final[set[str]] = set() + if context_1m_requested: + betas.add(ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_1M_2025_08_07.value) # Anthropic no longer requires the prompt-caching beta header # Prompt caching now works automatically when cache_control is used in messages # Reference: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching @@ -1347,8 +1321,6 @@ class AnthropicModelInfo(BaseLLMModelInfo): if is_vertex_request is True: # Vertex AI requires web search beta header for web search to work if web_search_tool_used: - from litellm.types.llms.anthropic import ANTHROPIC_BETA_HEADER_VALUES - headers["anthropic-beta"] = ANTHROPIC_BETA_HEADER_VALUES.WEB_SEARCH_2025_03_05.value elif len(all_betas) > 0: headers["anthropic-beta"] = ",".join(all_betas) @@ -1418,14 +1390,9 @@ class AnthropicModelInfo(BaseLLMModelInfo): user_anthropic_beta_headers: Final = self._get_user_anthropic_beta_headers( anthropic_beta_header=headers.get("anthropic-beta") ) - context_1m_supported: Final = context_1m_requested( - model=model, - optional_params=optional_params, - litellm_params=litellm_params, - ) anthropic_headers: Final = self.get_anthropic_headers( computer_tool_used=computer_tool_used, - context_1m_supported=context_1m_supported, + context_1m_requested=has_context_1m_suffix(model), prompt_caching_set=prompt_caching_set, pdf_used=pdf_used, api_key=resolved_api_key, diff --git a/litellm/llms/anthropic/pass_through/messages/transformation.py b/litellm/llms/anthropic/pass_through/messages/transformation.py index f6e20748b52..a8976c2b5ef 100644 --- a/litellm/llms/anthropic/pass_through/messages/transformation.py +++ b/litellm/llms/anthropic/pass_through/messages/transformation.py @@ -31,9 +31,8 @@ from litellm.types.router import GenericLiteLLMParams from ...common_utils import ( AnthropicError, AnthropicModelInfo, + has_context_1m_suffix, merge_anthropic_beta_headers, - context_1m_beta_values, - context_1m_requested, optionally_handle_anthropic_oauth, requires_native_compaction_beta, strip_advisor_blocks_from_messages, @@ -325,7 +324,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): model=model, ), ) - return self._finalize_messages_headers(headers, optional_params, messages), api_base + return self._finalize_messages_headers(headers, optional_params, messages, model=model), api_base async def avalidate_anthropic_messages_environment( self, @@ -365,7 +364,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): model=model, ), ) - return self._finalize_messages_headers(oauth_headers, optional_params, messages), api_base + return self._finalize_messages_headers(oauth_headers, optional_params, messages, model=model), api_base def _require_auth_header(self, auth_header: Mapping[str, str] | None, model: str) -> Mapping[str, str]: if auth_header is None: @@ -404,6 +403,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): headers: dict, # mutable-ok: out-param optional_params: dict, # mutable-ok: out-param messages: list[object], # mutable-ok: mirrors the validate_anthropic_messages_environment contract + model: str, ) -> dict: # mutable-ok: out-param if "anthropic-version" not in headers: headers["anthropic-version"] = DEFAULT_ANTHROPIC_API_VERSION @@ -414,7 +414,6 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): optional_params=optional_params, messages=messages, model=model, - litellm_params=litellm_params, ) @staticmethod @@ -765,7 +764,6 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): beta_map_provider: str | None = None, drop_params: bool = False, model: str = "", - litellm_params: object = None, ) -> dict: """ Auto-inject anthropic-beta headers based on features used. @@ -776,7 +774,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): - output_format: adds 'structured-outputs-2025-11-13' - speed: adds 'fast-mode-2026-02-01' - a message carrying output_config: adds 'per-turn-control-2026-07-01' - - [1m] suffix: adds 'context-1m-2025-08-07' + - [1m] model suffix: adds 'context-1m-2025-08-07' Args: headers: Request headers dict @@ -785,6 +783,7 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): messages: Request messages, scanned for per-message output_config beta_map_provider: Provider whose beta map column gates safeguards, defaults to custom_llm_provider drop_params: Whether the deployment or request set drop_params + model: Requested model name, checked for the [1m] suffix """ beta_values: Final[set[str]] = set() @@ -800,11 +799,8 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig): if requires_native_compaction_beta(custom_llm_provider, optional_params, messages): beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.COMPACT_2026_09_04.value) - beta_values.update( - context_1m_beta_values( - context_1m_requested(model=model, optional_params=optional_params, litellm_params=litellm_params) - ) - ) + if has_context_1m_suffix(model): + beta_values.add(ANTHROPIC_BETA_HEADER_VALUES.CONTEXT_1M_2025_08_07.value) # Check for context management context_management_param: Final = optional_params.get("context_management") diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index ba422a0a901..139ad07e0f3 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -798,6 +798,7 @@ class ANTHROPIC_BETA_HEADER_VALUES(str, Enum): WEB_FETCH_2025_09_10 = "web-fetch-2025-09-10" WEB_SEARCH_2025_03_05 = "web-search-2025-03-05" CONTEXT_MANAGEMENT_2025_06_27 = "context-management-2025-06-27" + CONTEXT_1M_2025_08_07 = "context-1m-2025-08-07" COMPACT_2026_01_12 = "compact-2026-01-12" COMPACT_2026_09_04 = "compact-2026-09-04" STRUCTURED_OUTPUT_2025_09_25 = "structured-outputs-2025-11-13" diff --git a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py index 32f18b952ac..6fe5be19fb9 100644 --- a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -9816,3 +9816,56 @@ def test_transform_request_tool_config_point_without_tools_drops_the_key(): assert "cache_control_injection_points" not in data assert "tools" not in data assert litellm_params["metadata"] == {} +@pytest.mark.parametrize("optional_params", [{}, {"thinking": {"type": "enabled", "budget_tokens": 2048}}]) +def test_transform_request_treats_context_1m_model_as_its_base_model(optional_params: dict[str, object]) -> None: + def transform(model: str) -> dict[str, object]: + return AnthropicConfig().transform_request( + model=model, + messages=[{"role": "user", "content": "hi"}], + optional_params=copy.deepcopy(optional_params), + litellm_params={}, + headers={}, + ) + + assert transform("claude-sonnet-4-6[1m]") == transform("claude-sonnet-4-6") + + +@pytest.mark.parametrize( + "model,expected_betas", + [ + ("anthropic/claude-sonnet-4-6[1m]", {"context-1m-2025-08-07", "interleaved-thinking-2025-05-14"}), + ("anthropic/claude-sonnet-4-6", {"interleaved-thinking-2025-05-14"}), + ], +) +async def test_acompletion_context_1m_suffix_sends_base_model_with_beta( + model: str, + expected_betas: set[str], + monkeypatch: pytest.MonkeyPatch, + respx_mock: respx.MockRouter, +) -> None: + monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True") + monkeypatch.setenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", "True") + monkeypatch.setattr(litellm.anthropic_beta_headers_manager, "_BETA_HEADERS_CONFIG", None) + route: Final = respx_mock.post("https://api.anthropic.com/v1/messages").respond( + 200, + json={ + "id": "msg_context_1m", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-6", + "content": [{"type": "text", "text": "hi"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 1, "output_tokens": 1}, + }, + ) + + await litellm.acompletion( + model=model, + messages=[{"role": "user", "content": "hi"}], + api_key="test", + extra_headers={"anthropic-beta": "interleaved-thinking-2025-05-14"}, + ) + + request: Final = route.calls.last.request + assert json.loads(request.content)["model"] == "claude-sonnet-4-6" + assert {beta.strip() for beta in request.headers["anthropic-beta"].split(",")} == expected_betas diff --git a/tests/unit/llms/anthropic/pass_through/messages/test_anthropic_messages_context_1m.py b/tests/unit/llms/anthropic/pass_through/messages/test_anthropic_messages_context_1m.py new file mode 100644 index 00000000000..4525769a7cf --- /dev/null +++ b/tests/unit/llms/anthropic/pass_through/messages/test_anthropic_messages_context_1m.py @@ -0,0 +1,57 @@ +import json +from typing import Final + +import pytest +import respx + +import litellm +from litellm import Router + + +@pytest.mark.parametrize( + "deployment_model,expected_betas", + [ + ("anthropic/claude-sonnet-4-6[1m]", {"context-1m-2025-08-07", "interleaved-thinking-2025-05-14"}), + ("anthropic/claude-sonnet-4-6", {"interleaved-thinking-2025-05-14"}), + ], +) +async def test_router_messages_context_1m_deployment_sends_base_model_with_beta( + deployment_model: str, + expected_betas: set[str], + monkeypatch: pytest.MonkeyPatch, + respx_mock: respx.MockRouter, +) -> None: + monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True") + monkeypatch.setenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", "True") + monkeypatch.setattr(litellm.anthropic_beta_headers_manager, "_BETA_HEADERS_CONFIG", None) + route: Final = respx_mock.post("https://api.anthropic.com/v1/messages").respond( + 200, + json={ + "id": "msg_context_1m", + "type": "message", + "role": "assistant", + "model": "claude-sonnet-4-6", + "content": [{"type": "text", "text": "hi"}], + "stop_reason": "end_turn", + "usage": {"input_tokens": 1, "output_tokens": 1}, + }, + ) + router: Final = Router( + model_list=[ + { + "model_name": "claude-sonnet-4-6[1m]", + "litellm_params": {"model": deployment_model, "api_key": "test"}, + } + ] + ) + + await router.aanthropic_messages( + model="claude-sonnet-4-6[1m]", + messages=[{"role": "user", "content": "hi"}], + max_tokens=16, + extra_headers={"anthropic-beta": "interleaved-thinking-2025-05-14"}, + ) + + request: Final = route.calls.last.request + assert json.loads(request.content)["model"] == "claude-sonnet-4-6" + assert {beta.strip() for beta in request.headers["anthropic-beta"].split(",")} == expected_betas diff --git a/tests/unit/llms/anthropic/test_anthropic_common_utils.py b/tests/unit/llms/anthropic/test_anthropic_common_utils.py index 093b4d6cce8..20afc5b7e40 100644 --- a/tests/unit/llms/anthropic/test_anthropic_common_utils.py +++ b/tests/unit/llms/anthropic/test_anthropic_common_utils.py @@ -3542,12 +3542,11 @@ class TestWifProviderAllowlist: monkeypatch.setenv("WIF_TEST_JWT", "jwt-assertion-value") def test_vertex_anthropic_never_mints_or_sends_the_assertion(self, monkeypatch, wif_engine): + import litellm from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation import ( VertexAIAnthropicConfig, ) - import litellm - poster, calls = wif_engine self._env_only_wif(monkeypatch) @@ -4522,3 +4521,39 @@ def test_shared_legacy_thinking_translation_drops_a_non_numeric_budget_under_dro ) assert optional_params == {"thinking": {"type": "adaptive"}} +@pytest.mark.parametrize( + "model,base_model", + [ + ("claude-sonnet-4-6[1m]", "claude-sonnet-4-6"), + ("claude-sonnet-4-6[1M]", "claude-sonnet-4-6"), + ("claude-sonnet-4-6", "claude-sonnet-4-6"), + ("claude-[1m]-sonnet-4-6", "claude-[1m]-sonnet-4-6"), + ("claude-sonnet-4-6[1m][1m]", "claude-sonnet-4-6[1m]"), + ("claude-sonnet-4-6[200k]", "claude-sonnet-4-6[200k]"), + ], +) +def test_context_1m_suffix_is_only_a_trailing_marker(model: str, base_model: str) -> None: + from litellm.llms.anthropic.common_utils import has_context_1m_suffix, strip_context_1m_suffix + + assert strip_context_1m_suffix(model) == base_model + assert has_context_1m_suffix(model) is (model != base_model) + + +@pytest.mark.parametrize("model,expects_context_1m", [("claude-sonnet-4-6[1m]", True), ("claude-sonnet-4-6", False)]) +def test_validate_environment_adds_context_1m_beta_only_for_suffixed_model( + model: str, expects_context_1m: bool +) -> None: + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + headers: Final = AnthropicModelInfo().validate_environment( + headers={"anthropic-beta": "interleaved-thinking-2025-05-14"}, + model=model, + messages=[{"role": "user", "content": "hi"}], + optional_params={}, + litellm_params={}, + api_key=FAKE_REGULAR_KEY, + ) + + betas: Final = {beta.strip() for beta in headers["anthropic-beta"].split(",")} + assert ("context-1m-2025-08-07" in betas) is expects_context_1m + assert "interleaved-thinking-2025-05-14" in betas diff --git a/tests/unit/llms/anthropic/test_context_1m_header.py b/tests/unit/llms/anthropic/test_context_1m_header.py deleted file mode 100644 index 505adb42501..00000000000 --- a/tests/unit/llms/anthropic/test_context_1m_header.py +++ /dev/null @@ -1,144 +0,0 @@ -from litellm.llms.anthropic.chat.transformation import AnthropicConfig -from litellm.llms.anthropic.common_utils import ( - context_1m_requested, - strip_context_1m_suffix, -) -from litellm.llms.anthropic.pass_through.messages.transformation import ( - AnthropicMessagesConfig, -) - - -class TestAnthropicContext1mBetaHeader: - def test_strip_context_1m_suffix_is_pure(self): - assert strip_context_1m_suffix("claude-sonnet-4-6[1m]") == "claude-sonnet-4-6" - assert strip_context_1m_suffix("claude-sonnet-4-6[1M]") == "claude-sonnet-4-6" - assert strip_context_1m_suffix("claude-sonnet-4-6") == "claude-sonnet-4-6" - assert strip_context_1m_suffix("gpt-4o[1m]") == "gpt-4o" - - def test_context_1m_requested_from_model_or_stashed_original(self): - assert context_1m_requested(model="claude-sonnet-4-6[1m]") - assert context_1m_requested( - model="claude-sonnet-4-6", - litellm_params={"_original_model": "claude-sonnet-4-6[1M]"}, - ) - assert not context_1m_requested(model="claude-sonnet-4-6") - assert not context_1m_requested(model="gpt-4o") - - def test_get_anthropic_headers_with_context_1m(self): - headers = AnthropicConfig().get_anthropic_headers(api_key="sk-test", context_1m_supported=True) - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_get_anthropic_headers_without_context_1m(self): - headers = AnthropicConfig().get_anthropic_headers(api_key="sk-test", context_1m_supported=False) - assert "context-1m-2025-08-07" not in headers.get("anthropic-beta", "") - - def test_validate_environment_adds_context_1m_for_suffixed_model(self): - headers = AnthropicConfig().validate_environment( - headers={}, - model="claude-sonnet-4-6[1m]", - messages=[{"role": "user", "content": "hi"}], - optional_params={}, - litellm_params={}, - api_key="sk-test", - ) - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_validate_environment_no_context_1m_without_suffix(self): - headers = AnthropicConfig().validate_environment( - headers={}, - model="claude-sonnet-4-6", - messages=[{"role": "user", "content": "hi"}], - optional_params={}, - litellm_params={}, - api_key="sk-test", - ) - assert "context-1m-2025-08-07" not in headers.get("anthropic-beta", "") - - def test_validate_environment_uses_original_model_from_litellm_params(self): - headers = AnthropicConfig().validate_environment( - headers={}, - model="claude-sonnet-4-6", - messages=[{"role": "user", "content": "hi"}], - optional_params={}, - litellm_params={"_original_model": "claude-sonnet-4-6[1M]"}, - api_key="sk-test", - ) - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_update_headers_adds_context_1m_with_original_model(self): - headers = AnthropicConfig().update_headers_with_optional_anthropic_beta( - headers={}, - optional_params={"_original_model": "claude-sonnet-4-6[1m]"}, - ) - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_update_headers_no_context_1m_without_original_model(self): - headers = AnthropicConfig().update_headers_with_optional_anthropic_beta( - headers={}, - optional_params={}, - ) - assert "context-1m-2025-08-07" not in headers.get("anthropic-beta", "") - - def test_pass_through_adds_context_1m(self): - headers = AnthropicMessagesConfig._update_headers_with_anthropic_beta( - headers={}, - optional_params={"_original_model": "claude-sonnet-4-6[1m]"}, - ) - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_pass_through_no_context_1m_without_suffix(self): - headers = AnthropicMessagesConfig._update_headers_with_anthropic_beta( - headers={}, - optional_params={}, - ) - assert "context-1m-2025-08-07" not in headers.get("anthropic-beta", "") - - def test_pass_through_adds_context_1m_from_model_without_original_model(self): - headers, _ = AnthropicMessagesConfig().validate_anthropic_messages_environment( - headers={}, - model="claude-sonnet-4-6[1m]", - messages=[{"role": "user", "content": "hi"}], - optional_params={}, - litellm_params={}, - api_key="sk-test", - ) - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_pass_through_transform_strips_1m_from_upstream_model(self): - result = AnthropicMessagesConfig().transform_anthropic_messages_request( - model="claude-sonnet-4-6[1m]", - messages=[{"role": "user", "content": "hi"}], - anthropic_messages_optional_request_params={"max_tokens": 16}, - litellm_params={}, - headers={}, - ) - assert result["model"] == "claude-sonnet-4-6" - - def test_chat_transform_request_strips_1m_and_injects_header(self): - headers = {} - result = AnthropicConfig().transform_request( - model="claude-sonnet-4-6[1m]", - messages=[{"role": "user", "content": "hi"}], - optional_params={"max_tokens": 16}, - litellm_params={}, - headers=headers, - ) - assert result["model"] == "claude-sonnet-4-6" - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_chat_transform_request_uppercase_suffix_strips_and_injects_header(self): - headers = {} - result = AnthropicConfig().transform_request( - model="claude-sonnet-4-6[1M]", - messages=[{"role": "user", "content": "hi"}], - optional_params={"max_tokens": 16}, - litellm_params={}, - headers=headers, - ) - assert result["model"] == "claude-sonnet-4-6" - assert "context-1m-2025-08-07" in headers.get("anthropic-beta", "") - - def test_proxy_does_not_globally_strip_non_anthropic_suffix(self): - from litellm.proxy import route_llm_request - - assert not hasattr(route_llm_request, "stash_and_strip_context_1m_model_suffix")