mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(anthropic/passthrough): drop incompatible temperature when downgrading adaptive thinking for pre-4.6 models (#33244)
* fix(anthropic/passthrough): drop temperature and cap thinking budget when downgrading adaptive thinking for pre-4.6 models
* test(anthropic/passthrough): use sufficient max_tokens for reasoning_effort thinking mapping
* fix(anthropic/passthrough): drop incompatible temperature when downgrading adaptive thinking for pre-4.6 models
Narrow the fix to the temperature reconciliation; the reasoning_effort
budget cap is reverted because the live translation grid relies on
budget_tokens >= max_tokens to reject unsupported effort tiers
(xhigh/max) on budget-mode models, so capping turned those 400s into
200s.
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit 71dffc1e9a)
This commit is contained in:
parent
b3086ccd74
commit
6fec38dabd
2 changed files with 112 additions and 0 deletions
|
|
@ -386,6 +386,36 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
return thinking
|
||||
return {**thinking, "budget_tokens": max_tokens - 1}
|
||||
|
||||
@staticmethod
|
||||
def _drop_incompatible_temperature_for_thinking(
|
||||
model: str, optional_params: dict, custom_llm_provider: str
|
||||
) -> None:
|
||||
"""Anthropic rejects any ``temperature`` other than 1 while extended thinking
|
||||
is enabled ("temperature may only be set to 1 when thinking is enabled").
|
||||
|
||||
Clients like Claude Code send ``thinking``/``output_config.effort`` together
|
||||
with a pinned ``temperature`` (e.g. the safety classifier uses ``temperature=0``
|
||||
for determinism). When the request lands on a non-adaptive model, the effort
|
||||
interface is reshaped above into legacy ``thinking={type: enabled}`` (or kept
|
||||
as ``output_config.effort`` on Opus 4.5), and the leftover ``temperature`` would
|
||||
400. Preserving the thinking the caller asked for wins over an unhonorable
|
||||
sampling value (Anthropic forces ``temperature=1`` under thinking regardless),
|
||||
so drop it and let the API default apply.
|
||||
|
||||
Adaptive models (4.6+) own this natively and are left untouched.
|
||||
"""
|
||||
if AnthropicModelInfo._is_adaptive_thinking_model(model, custom_llm_provider):
|
||||
return
|
||||
temperature = optional_params.get("temperature")
|
||||
if temperature is None or temperature == 1:
|
||||
return
|
||||
thinking = optional_params.get("thinking")
|
||||
output_config = optional_params.get("output_config")
|
||||
thinking_enabled = isinstance(thinking, dict) and thinking.get("type") == "enabled"
|
||||
effort_enabled = isinstance(output_config, dict) and output_config.get("effort") is not None
|
||||
if thinking_enabled or effort_enabled:
|
||||
optional_params.pop("temperature", None)
|
||||
|
||||
def transform_anthropic_messages_request(
|
||||
self,
|
||||
model: str,
|
||||
|
|
@ -426,6 +456,12 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
custom_llm_provider=self._resolved_provider,
|
||||
)
|
||||
|
||||
self._drop_incompatible_temperature_for_thinking(
|
||||
model=model,
|
||||
optional_params=anthropic_messages_optional_request_params,
|
||||
custom_llm_provider=self._resolved_provider,
|
||||
)
|
||||
|
||||
system_param = anthropic_messages_optional_request_params.get("system")
|
||||
if self.should_strip_billing_metadata() and system_param is not None:
|
||||
filtered_system = self._filter_billing_headers_from_system(system_param)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import pytest
|
|||
|
||||
from litellm.constants import (
|
||||
DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_XHIGH_THINKING_BUDGET,
|
||||
)
|
||||
|
|
@ -174,6 +175,81 @@ def test_unrecognized_effort_raises_clean_400():
|
|||
assert exc_info.value.status_code == 400
|
||||
|
||||
|
||||
def test_pinned_temperature_dropped_when_adaptive_downgraded_to_enabled():
|
||||
"""Regression (#33203): Claude Code's safety classifier sends adaptive thinking +
|
||||
temperature=0 to Haiku 4.5. The adaptive interface is downgraded to legacy enabled
|
||||
thinking, but Anthropic rejects "temperature may only be set to 1 when thinking is
|
||||
enabled". The pinned temperature must be dropped so the request succeeds while the
|
||||
downgraded thinking is preserved."""
|
||||
params = _claude_code_payload(effort="medium")
|
||||
params["temperature"] = 0
|
||||
result = _transform("claude-haiku-4-5", params)
|
||||
|
||||
assert result["thinking"] == {
|
||||
"type": "enabled",
|
||||
"budget_tokens": DEFAULT_REASONING_EFFORT_MEDIUM_THINKING_BUDGET,
|
||||
}
|
||||
assert "temperature" not in result
|
||||
|
||||
|
||||
def test_temperature_one_preserved_with_enabled_thinking():
|
||||
"""temperature=1 is compatible with extended thinking, so it must be kept."""
|
||||
params = _claude_code_payload(effort="medium")
|
||||
params["temperature"] = 1
|
||||
result = _transform("claude-haiku-4-5", params)
|
||||
|
||||
assert result["thinking"]["type"] == "enabled"
|
||||
assert result["temperature"] == 1
|
||||
|
||||
|
||||
def test_pinned_temperature_preserved_when_thinking_dropped():
|
||||
"""When thinking is dropped entirely (non-reasoning model), there is no thinking
|
||||
conflict, so a pinned temperature must survive untouched."""
|
||||
params = _claude_code_payload(effort="medium")
|
||||
params["temperature"] = 0
|
||||
result = _transform("claude-3-5-haiku-latest", params)
|
||||
|
||||
assert "thinking" not in result
|
||||
assert result["temperature"] == 0
|
||||
|
||||
|
||||
def test_pinned_temperature_preserved_for_adaptive_model():
|
||||
"""Adaptive models (4.6+) own the thinking/temperature relationship natively, so
|
||||
the passthrough must not strip a pinned temperature for them."""
|
||||
params = _claude_code_payload(effort="high")
|
||||
params["temperature"] = 0
|
||||
result = _transform("claude-sonnet-4-6", params)
|
||||
|
||||
assert result["thinking"] == {"type": "adaptive"}
|
||||
assert result["temperature"] == 0
|
||||
|
||||
|
||||
def test_pinned_temperature_dropped_for_opus_4_5_effort():
|
||||
"""Opus 4.5 keeps native output_config.effort (extended thinking), which is equally
|
||||
incompatible with a pinned non-1 temperature, so the temperature must be dropped."""
|
||||
params = _claude_code_payload(effort="medium")
|
||||
params["temperature"] = 0
|
||||
result = _transform("claude-opus-4-5", params)
|
||||
|
||||
assert result["output_config"] == {"effort": "medium"}
|
||||
assert "temperature" not in result
|
||||
|
||||
|
||||
def test_reasoning_effort_with_pinned_temperature_drops_temperature():
|
||||
"""The reasoning_effort alias synthesizes legacy enabled thinking on a non-adaptive
|
||||
model; a co-pinned non-1 temperature must be dropped to avoid the Anthropic 400."""
|
||||
result = _transform(
|
||||
"claude-haiku-4-5",
|
||||
{"max_tokens": 8192, "reasoning_effort": "low", "temperature": 0},
|
||||
)
|
||||
|
||||
assert result["thinking"] == {
|
||||
"type": "enabled",
|
||||
"budget_tokens": DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET,
|
||||
}
|
||||
assert "temperature" not in result
|
||||
|
||||
|
||||
def test_non_adaptive_request_without_effort_is_untouched():
|
||||
"""A non-adaptive model receiving a request with no adaptive interface (no
|
||||
effort, no adaptive thinking) must pass through untouched."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue