fix: omit thinking.type=disabled for always-on thinking Claude models (#37510)

This commit is contained in:
tin-berri 2026-08-21 10:27:26 -07:00 committed by GitHub
parent c1662258df
commit 4307b34aca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 253 additions and 239 deletions

View file

@ -27,6 +27,7 @@ EXTRA_BOOLEAN_KEYS = frozenset(
"uses_embed_content",
"use_openai_responses_path",
"bedrock_converse_supports_strict_tools",
"thinking_always_on",
}
)

View file

@ -1827,6 +1827,12 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
custom_llm_provider=self.custom_llm_provider,
)
AnthropicModelInfo.maybe_drop_disabled_thinking(
model=model,
optional_params=optional_params,
custom_llm_provider=self._resolved_provider,
)
headers = self.update_headers_with_optional_anthropic_beta(headers=headers, optional_params=optional_params)
# === Tool-name sanitization (single chokepoint) ===

View file

@ -32,6 +32,12 @@ from litellm.types.llms.anthropic import (
from litellm.types.llms.openai import AllMessageValues
from litellm.types.proxy.model_listing import ModelInfoResponse
DROP_DISABLED_THINKING_WARNING: Final = (
"Dropping `thinking={'type': 'disabled'}` for model=%s: thinking is always on for this model and cannot be "
"disabled (the alternative is a provider 400). The model will still think adaptively, its response can contain "
"thinking blocks, and those thinking tokens are billed as output tokens."
)
_BEDROCK_VERSION_SUFFIX_RE: Final = re.compile(r"-v\d+(?::\d+)?$")
_INFERENCE_PROFILE_MINOR_RE: Final = re.compile(r":\d+$")
_DATED_RELEASE_SUFFIX_RE: Final = re.compile(r"-\d{8}$")
@ -425,6 +431,35 @@ class AnthropicModelInfo(BaseLLMModelInfo):
"""
return AnthropicModelInfo._supports_model_capability(model, "supports_adaptive_thinking", custom_llm_provider)
@staticmethod
def _is_always_on_thinking_model(model: str, custom_llm_provider: str) -> bool:
"""Whether ``model`` always thinks and rejects ``thinking.type=disabled``
(Fable 5 / Mythos 5 generation). The model cost map is authoritative: an
explicit ``thinking_always_on`` entry resolved under ``custom_llm_provider``,
or a ``fallback_generalizations`` rule for unmapped ids of those families.
"""
return AnthropicModelInfo._supports_model_capability(model, "thinking_always_on", custom_llm_provider)
@staticmethod
def maybe_drop_disabled_thinking(
model: str,
optional_params: dict, # mutable-ok: in-place out-param, same contract as AnthropicConfig._maybe_drop_speed_param
custom_llm_provider: str,
) -> None:
"""Omit ``thinking={'type': 'disabled'}`` for always-on-thinking models
(Fable 5 / Mythos 5), which 400 on it; omission is the API-documented
remedy and yields the model's default adaptive thinking."""
thinking: Final = optional_params.get("thinking")
if not isinstance(thinking, dict) or thinking.get("type") != "disabled":
return
if not AnthropicModelInfo._is_always_on_thinking_model(model, custom_llm_provider):
return
litellm.verbose_logger.warning(
DROP_DISABLED_THINKING_WARNING,
model,
)
optional_params.pop("thinking", None)
def is_effort_used(
self,
optional_params: dict | None,

View file

@ -568,6 +568,12 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
custom_llm_provider=self._resolved_provider,
)
AnthropicModelInfo.maybe_drop_disabled_thinking(
model=model,
optional_params=anthropic_messages_optional_request_params,
custom_llm_provider=self._resolved_provider,
)
self._translate_legacy_thinking_for_adaptive_model(
model=model,
optional_params=anthropic_messages_optional_request_params,

View file

@ -39,6 +39,7 @@ from litellm.llms.anthropic.chat.transformation import (
REASONING_EFFORT_TO_OUTPUT_CONFIG_EFFORT,
AnthropicConfig,
)
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
from litellm.llms.base_llm.chat.transformation import BaseConfig, BaseLLMException
from litellm.llms.bedrock.request_metadata import (
bedrock_request_metadata_headers,
@ -1571,6 +1572,12 @@ class AmazonConverseConfig(BaseConfig):
"has no thinking_blocks. The model won't use extended thinking for this turn."
)
AnthropicModelInfo.maybe_drop_disabled_thinking(
model=model,
optional_params=optional_params,
custom_llm_provider="bedrock",
)
# Prepare and separate parameters
(
inference_params,

View file

@ -1232,6 +1232,7 @@
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "chat",
"thinking_always_on": true,
"supports_function_calling": true,
"supports_vision": true,
"supports_prompt_caching": false,
@ -1404,6 +1405,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -1440,6 +1442,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -1476,6 +1479,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -1512,6 +1516,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -3021,6 +3026,7 @@
"cache_creation_input_token_cost_above_1hr": 2e-05,
"cache_read_input_token_cost": 1e-06,
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -12780,6 +12786,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -40365,6 +40372,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -40398,6 +40406,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -49754,6 +49763,7 @@
},
"source": "https://docs.claude.com/en/docs/about-claude/models/overview",
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -49789,6 +49799,7 @@
},
"source": "https://docs.claude.com/en/docs/about-claude/models/overview",
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -49967,6 +49978,14 @@
"supports_adaptive_thinking": true
}
},
{
"name": "claude-always-on-thinking",
"pattern": "claude-(?:fable|mythos)-",
"description": "Any Claude Fable or Mythos id, under any provider namespace and any version. These families always think and reject thinking.type=disabled with a 400; the Anthropic transformations omit the param instead, so the model falls back to its default adaptive thinking.",
"model_info": {
"thinking_always_on": true
}
},
{
"name": "claude-mid-conversation-system",
"pattern": "claude-[a-z]+-(?:4[-._](?:[89]|[1-9]\\d)(?!\\d)|(?:[5-9]|[1-9]\\d)(?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)",

View file

@ -683,7 +683,7 @@ ANTHROPIC_API_ONLY_HEADERS: Final = { # fails if calling anthropic on vertex ai
class AnthropicThinkingParam(TypedDict, total=False):
type: Literal["enabled", "adaptive"]
type: ReadOnly[Literal["enabled", "adaptive", "disabled"]]
budget_tokens: int

View file

@ -154,6 +154,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
supports_web_search: bool | None
supports_reasoning: bool | None
supports_adaptive_thinking: bool | None
thinking_always_on: ReadOnly[bool | None]
supports_tool_search: bool | None
supports_mid_conversation_system: bool | None
supports_url_context: bool | None

View file

@ -5753,6 +5753,7 @@ def _get_model_info_helper(
supports_url_context=_model_info.get("supports_url_context", None),
supports_reasoning=_model_info.get("supports_reasoning", None),
supports_adaptive_thinking=_model_info.get("supports_adaptive_thinking", None),
thinking_always_on=_model_info.get("thinking_always_on", None),
supports_tool_search=_model_info.get("supports_tool_search", None),
supports_mid_conversation_system=_model_info.get("supports_mid_conversation_system", None),
supports_none_reasoning_effort=_model_info.get("supports_none_reasoning_effort", None),

View file

@ -1232,6 +1232,7 @@
"max_output_tokens": 128000,
"max_tokens": 128000,
"mode": "chat",
"thinking_always_on": true,
"supports_function_calling": true,
"supports_vision": true,
"supports_prompt_caching": false,
@ -1404,6 +1405,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -1440,6 +1442,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -1476,6 +1479,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -1512,6 +1516,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -3021,6 +3026,7 @@
"cache_creation_input_token_cost_above_1hr": 2e-05,
"cache_read_input_token_cost": 1e-06,
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -12780,6 +12786,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -40365,6 +40372,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -40398,6 +40406,7 @@
"search_context_size_medium": 0.01
},
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -49754,6 +49763,7 @@
},
"source": "https://docs.claude.com/en/docs/about-claude/models/overview",
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_mid_conversation_system": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
@ -49789,6 +49799,7 @@
},
"source": "https://docs.claude.com/en/docs/about-claude/models/overview",
"supports_adaptive_thinking": true,
"thinking_always_on": true,
"supports_assistant_prefill": false,
"supports_computer_use": true,
"supports_function_calling": true,
@ -49967,6 +49978,14 @@
"supports_adaptive_thinking": true
}
},
{
"name": "claude-always-on-thinking",
"pattern": "claude-(?:fable|mythos)-",
"description": "Any Claude Fable or Mythos id, under any provider namespace and any version. These families always think and reject thinking.type=disabled with a 400; the Anthropic transformations omit the param instead, so the model falls back to its default adaptive thinking.",
"model_info": {
"thinking_always_on": true
}
},
{
"name": "claude-mid-conversation-system",
"pattern": "claude-[a-z]+-(?:4[-._](?:[89]|[1-9]\\d)(?!\\d)|(?:[5-9]|[1-9]\\d)(?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)",

View file

@ -706,6 +706,9 @@
"supports_xhigh_reasoning_effort": {
"type": "boolean"
},
"thinking_always_on": {
"type": "boolean"
},
"tiered_pricing": {
"type": "array",
"description": "Context-length or result-count tiered rates; each tier's costs apply within its range.",

View file

@ -12,7 +12,7 @@
"limit": 768
},
"TQ005": {
"limit": 2832
"limit": 2810
},
"TQ006": {
"limit": 34

View file

@ -188,6 +188,25 @@ def secret_vault_factory():
return FakeSecretVault
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled in-repo cost map so capability and pricing assertions do not
depend on the network-fetched ``main`` copy, which lags this branch until merge.
``get_model_info`` is lru_cached, so swapping ``model_cost`` is not enough on its
own; clear on the way in and out so entries warmed against either map never leak
across tests."""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def _run_coroutine_if_needed(result):
if not asyncio.iscoroutine(result):
return

View file

@ -15,13 +15,7 @@ sys.path.insert(
) # Adds the parent directory to the system path
@pytest.fixture
def local_model_cost_map(monkeypatch):
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
# Test basic web search cost calculations
def test_web_search_cost_low():
web_search_options = WebSearchOptions(search_context_size="low")
model_info = litellm.get_model_info("gpt-4o-search-preview")

View file

@ -366,6 +366,17 @@ def test_shipped_rules_stack_adaptive_and_mid_conversation_flags(shipped_cost_ma
assert info["supports_function_calling"] is True
def test_shipped_rules_flag_unmapped_fable_as_always_on_thinking(shipped_cost_map):
"""An unmapped Fable/Mythos id picks up ``thinking_always_on`` from the
claude-always-on-thinking rule, while other unmapped Claudes stay unflagged."""
model = "claude-fable-5-1"
assert model not in litellm.model_cost
info = litellm.get_model_info(model, custom_llm_provider="anthropic")
assert info["thinking_always_on"] is True
other = litellm.get_model_info("claude-opus-4-9", custom_llm_provider="anthropic")
assert other.get("thinking_always_on") is None
@pytest.mark.parametrize(
"model,provider",
[

View file

@ -2813,18 +2813,6 @@ def test_raw_adaptive_thinking_untouched_for_46_plus_model():
assert result["thinking"] == {"type": "adaptive"}
@pytest.fixture
def local_model_cost_map(monkeypatch):
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
@pytest.mark.parametrize(
"model, expected",
@ -6143,3 +6131,41 @@ def test_is_anthropic_usage_object_rejects_responses_api_usage():
"output_tokens_details": {"reasoning_tokens": 0},
}
)
@pytest.mark.parametrize(
"model, expected_dropped",
[
# always-on-thinking models reject thinking.type=disabled with a 400
("claude-fable-5", True),
("claude-mythos-5", True),
# unmapped future family member -> claude-always-on-thinking fallback rule
("claude-fable-5-1", True),
# adaptive-capable models that ACCEPT disabled must keep it verbatim
("claude-opus-5", False),
("claude-sonnet-5", False),
("claude-opus-4-8", False),
# legacy models keep it verbatim
("claude-sonnet-4-5-20250929", False),
],
)
def test_disabled_thinking_omitted_only_for_always_on_models(
local_model_cost_map, model, expected_dropped
):
"""``thinking={"type": "disabled"}`` is omitted for always-on-thinking models
(Fable/Mythos, which 400 on it: the API remedy is to omit the param) and is
forwarded verbatim for every model that accepts it."""
config = AnthropicConfig()
request = config.transform_request(
model=model,
messages=[{"role": "user", "content": "hi"}],
optional_params={"max_tokens": 64, "thinking": {"type": "disabled"}},
litellm_params={},
headers={},
)
if expected_dropped:
assert "thinking" not in request
else:
assert request["thinking"] == {"type": "disabled"}

View file

@ -17,21 +17,6 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so Opus 4.8 adaptive detection (driven
by the ``supports_adaptive_thinking`` flag) doesn't depend on the
network-fetched ``main`` copy, which lacks the flag until this branch merges."""
original = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original
litellm.get_model_info.cache_clear()
@pytest.mark.parametrize(
"reasoning_effort,expected_effort",
@ -424,3 +409,33 @@ def test_legacy_thinking_left_untouched_on_non_adaptive_model():
assert result.get("thinking") == {"type": "enabled", "budget_tokens": 31999}
assert "output_config" not in result
@pytest.mark.parametrize(
"model, expected_dropped",
[
("claude-fable-5", True),
("claude-opus-5", False),
("claude-sonnet-4-5", False),
],
)
def test_disabled_thinking_omitted_for_always_on_models_messages(
local_model_cost_map, model, expected_dropped
):
"""/v1/messages: ``thinking={"type": "disabled"}`` is omitted for always-on-thinking
models and forwarded verbatim for models that accept it."""
config = AnthropicMessagesConfig()
optional_params = {"max_tokens": 64, "thinking": {"type": "disabled"}}
result = config.transform_anthropic_messages_request(
model=model,
messages=[{"role": "user", "content": "Hello"}],
anthropic_messages_optional_request_params=optional_params,
litellm_params={},
headers={},
)
if expected_dropped:
assert "thinking" not in result
else:
assert result["thinking"] == {"type": "disabled"}

View file

@ -1742,22 +1742,6 @@ class TestAnthropicThinkingSignatureSelfHeal:
assert data["messages"] == []
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so detection doesn't depend on the
network-fetched ``main`` copy (which lacks this branch's flags until merge)."""
import litellm
original = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original
litellm.get_model_info.cache_clear()
class TestClaudeOpus48AdaptiveThinking:
"""Opus 4.8 requires adaptive thinking (``thinking.type='adaptive'`` +

View file

@ -317,21 +317,6 @@ class TestProviderConfigManagerAzureAnthropicMessages:
assert config is None
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so capability flags match this branch."""
import litellm
original = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original
litellm.get_model_info.cache_clear()
def test_messages_thinking_shape_follows_exact_azure_entry_flag(local_model_cost_map, monkeypatch):
"""The Azure messages config must probe capabilities under ``azure_ai`` so an

View file

@ -6133,3 +6133,34 @@ def test_update_optional_params_with_thinking_tokens_bool_thinking_does_not_cras
non_default_params={"thinking": True}, optional_params=optional_params
)
assert "maxTokens" not in optional_params
@pytest.mark.parametrize(
"model, expected_dropped",
[
("anthropic.claude-fable-5", True),
("us.anthropic.claude-fable-5", True),
("us.anthropic.claude-opus-4-8", False),
],
)
def test_disabled_thinking_omitted_for_always_on_models_converse(
local_model_cost_map, model, expected_dropped
):
"""Bedrock Converse: ``thinking={"type": "disabled"}`` is omitted for always-on-thinking
models and forwarded verbatim for models that accept it."""
config = AmazonConverseConfig()
result = config._transform_request(
model=model,
messages=[{"role": "user", "content": "hi"}],
optional_params={"maxTokens": 64, "thinking": {"type": "disabled"}},
litellm_params={},
headers={},
)
additional = result.get("additionalModelRequestFields", {})
if expected_dropped:
assert "thinking" not in additional
else:
assert additional.get("thinking") == {"type": "disabled"}

View file

@ -31,23 +31,6 @@ from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_tran
)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so adaptive-thinking detection reads this
branch's ``supports_adaptive_thinking`` flags, which the network-fetched
``main`` copy lacks until merge."""
import litellm
original = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original
litellm.get_model_info.cache_clear()
@pytest.mark.asyncio
async def test_bedrock_sse_wrapper_encodes_dict_chunks():

View file

@ -21,14 +21,6 @@ WEB_SEARCH_MODELS = (
COMPOUND_MODELS = ("compound", "compound-mini", "groq/compound", "groq/compound-mini")
@pytest.fixture
def local_model_cost_map(monkeypatch: pytest.MonkeyPatch):
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
litellm.get_model_info.cache_clear()
yield
litellm.get_model_info.cache_clear()
class TestGroqWebSearchOptions:
@pytest.mark.parametrize("model", WEB_SEARCH_MODELS + COMPOUND_MODELS)

View file

@ -51,16 +51,6 @@ def test_ocr4_cost_scales_with_pages(model: str, pages_processed: int) -> None:
assert cost == pytest.approx(OCR4_COST_PER_PAGE * pages_processed)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force get_model_info to resolve against the in-repo cost map instead of the
remote one fetched at import time, which does not yet carry OCR 3 pricing."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
litellm.get_model_info.cache_clear()
yield
litellm.get_model_info.cache_clear()
@pytest.mark.parametrize("cost_map_path", [MAIN_COST_MAP, BACKUP_COST_MAP])
def test_ocr3_pricing_entry(cost_map_path: Path) -> None:

View file

@ -5,18 +5,6 @@ from litellm.llms.tencent.cost_calculator import cost_per_token
from litellm.types.utils import Usage
@pytest.fixture
def local_model_cost_map(monkeypatch):
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def test_cost_per_token_uses_tencent_model_pricing(local_model_cost_map):
usage = Usage(prompt_tokens=1000, completion_tokens=2000, total_tokens=3000)

View file

@ -514,21 +514,6 @@ def test_vertex_claude_completion_does_not_mutate_shared_extra_headers():
), "extra_headers must not be mutated by completion()"
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so capability flags match this branch."""
import litellm
original = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original
litellm.get_model_info.cache_clear()
def test_messages_thinking_shape_follows_exact_vertex_entry_flag(local_model_cost_map, monkeypatch):
"""The Vertex messages config must probe capabilities under ``vertex_ai`` so an

View file

@ -26,25 +26,12 @@ OPUS_4_6_MIN_TOKENS = 4096
@pytest.fixture(autouse=True)
def local_model_cost_map(monkeypatch):
"""
The remote cost map does not carry `prompt_cache_min_tokens` yet, so a test that reads the
default map would pass here and flake in CI. Force the in-repo map.
def _local_model_cost_map_autouse(local_model_cost_map):
"""Every test here reads `prompt_cache_min_tokens`, which only the in-repo map
carries, so the shared local_model_cost_map fixture (conftest.py) is autouse
for the whole file."""
yield
`get_model_info` is lru_cached, so swapping `model_cost` is not enough on its own: an earlier
test that resolved these models against the remote map leaves entries with no
`prompt_cache_min_tokens`, and the stale hit resolves to the default. Clear on the way out too,
so the entries these tests warm against the local map do not leak into later tests.
"""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def _deployments(*models: str) -> List[dict]:

View file

@ -27,20 +27,6 @@ def _load_root_cost_map() -> dict:
return json.load(f)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so assertions don't depend on the
network-fetched ``main`` copy (which lags this branch until merge)."""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def test_fable_5_model_pricing_and_capabilities():
model_data = _load_root_cost_map()
@ -186,6 +172,23 @@ def test_fable_5_all_variants_carry_adaptive_thinking_flag(cost_map):
assert not missing, f"missing supports_adaptive_thinking: {missing}"
@pytest.mark.parametrize(
"cost_map",
[_load_root_cost_map(), GetModelCostMap.load_local_model_cost_map()],
ids=["root", "bundled_backup"],
)
def test_fable_5_all_variants_carry_thinking_always_on_flag(cost_map):
"""Every Fable 5 entry must advertise ``thinking_always_on``.
The flag drives the Anthropic transformations to omit an explicit
``thinking.type='disabled'``, which Fable 5 rejects with a 400; a variant
missing the flag forwards the param verbatim and the provider 400s."""
variants = [k for k in cost_map if "claude-fable-5" in k]
assert variants, "no claude-fable-5 entries found in cost map"
missing = [k for k in variants if cost_map[k].get("thinking_always_on") is not True]
assert not missing, f"missing thinking_always_on: {missing}"
@pytest.mark.parametrize(
"model",
[

View file

@ -29,20 +29,6 @@ def _load_root_cost_map() -> dict:
return json.load(f)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so assertions don't depend on the
network-fetched ``main`` copy (which lags this branch until merge)."""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def test_opus_4_8_model_pricing_and_capabilities():
model_data = _load_root_cost_map()

View file

@ -52,20 +52,6 @@ def _load_root_cost_map() -> dict:
return json.load(f)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so assertions don't depend on the
network-fetched ``main`` copy (which lags this branch until merge)."""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def test_opus_5_pricing_and_capabilities():
model_data = _load_root_cost_map()

View file

@ -41,20 +41,6 @@ def _load_root_cost_map() -> dict:
return json.load(f)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so assertions don't depend on the
network-fetched ``main`` copy (which lags this branch until merge)."""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def test_sonnet_5_pricing_and_capabilities():
model_data = _load_root_cost_map()

View file

@ -27,16 +27,6 @@ def _load(path):
return json.load(f)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force get_model_info to resolve against the in-repo cost map instead of the
remote one fetched at import time, which still carries the pre-merge pricing."""
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
monkeypatch.setattr(litellm, "model_cost", litellm.get_model_cost_map(url=""))
litellm.get_model_info.cache_clear()
yield
litellm.get_model_info.cache_clear()
@pytest.mark.parametrize("model", MEDIUM_3_5_MODELS)
def test_medium_3_5_specs(model):

View file

@ -23,20 +23,6 @@ def _load_cost_map(filename: str = "model_prices_and_context_window.json") -> di
return json.load(f)
@pytest.fixture
def local_model_cost_map(monkeypatch):
"""Force the bundled backup cost map so assertions don't depend on the
network-fetched ``main`` copy (which lags this branch until merge)."""
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
@pytest.mark.parametrize("model, input_cost, cached_cost, output_cost", PRICING)
def test_muse_spark_1_2_model_info(model: str, input_cost: float, cached_cost: float, output_cost: float):

View file

@ -101,18 +101,6 @@ def test_prompt_tokens_details_cache_write_creation_stay_in_sync_on_assignment()
assert details.cache_write_tokens == details.cache_creation_tokens == 375
@pytest.fixture
def local_model_cost_map(monkeypatch):
original_model_cost = litellm.model_cost
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
litellm.get_model_info.cache_clear()
try:
yield
finally:
litellm.model_cost = original_model_cost
litellm.get_model_info.cache_clear()
def test_get_model_info_surfaces_supports_adaptive_thinking(local_model_cost_map):
"""supports_adaptive_thinking must flow through get_model_info like every other
@ -1009,6 +997,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"supports_xhigh_reasoning_effort": {"type": "boolean"},
"supports_max_reasoning_effort": {"type": "boolean"},
"supports_adaptive_thinking": {"type": "boolean"},
"thinking_always_on": {"type": "boolean"},
"supports_mid_conversation_system": {"type": "boolean"},
"supports_sampling_params": {"type": "boolean"},
"supports_output_config": {"type": "boolean"},

View file

@ -33,6 +33,6 @@
"limit": 5588
},
"LIT012": {
"limit": 4511
"limit": 4510
}
}