mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-14 04:09:09 +00:00
Merge 79efcc892e into a85db3c68b
This commit is contained in:
commit
63ee3428b7
2 changed files with 85 additions and 32 deletions
|
|
@ -40,6 +40,7 @@ from .mid_conversation_system import (
|
|||
DEFAULT_ANTHROPIC_API_VERSION: Final = "2023-06-01"
|
||||
|
||||
_CALLER_CREDENTIAL_HEADERS: Final = frozenset({"x-api-key", "authorization"})
|
||||
_MIN_TOP_P_WITH_THINKING: Final = 0.95
|
||||
|
||||
|
||||
def _carries_caller_credential(headers: Mapping[str, str]) -> bool:
|
||||
|
|
@ -540,34 +541,39 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
optional_params.pop("output_config", None)
|
||||
|
||||
@staticmethod
|
||||
def _drop_incompatible_temperature_for_thinking(
|
||||
model: str, optional_params: dict, custom_llm_provider: str
|
||||
def _drop_sampling_params_incompatible_with_thinking(
|
||||
model: str, optional_params: dict[str, object], 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").
|
||||
"""While thinking is on, Anthropic rejects ``temperature`` other than 1, any
|
||||
``top_k``, and ``top_p`` below 0.95, for legacy ``enabled`` and ``adaptive``
|
||||
thinking alike ("temperature may only be set to 1 when thinking is enabled or
|
||||
in adaptive mode").
|
||||
|
||||
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.
|
||||
Clients like Claude Code send thinking together with a pinned ``temperature``
|
||||
(the safety classifier uses ``temperature=0``). Preserving the thinking the
|
||||
caller asked for wins over a sampling value the API would refuse, so drop it
|
||||
and let the API default apply. On a non-adaptive model a bare
|
||||
``output_config.effort`` (kept natively on Opus 4.5) also means thinking is on,
|
||||
unless the caller explicitly disabled thinking.
|
||||
"""
|
||||
if AnthropicModelInfo.is_adaptive_thinking_model(model, custom_llm_provider):
|
||||
return
|
||||
temperature: Final = optional_params.get("temperature")
|
||||
if temperature is None or temperature == 1:
|
||||
return
|
||||
thinking: Final = optional_params.get("thinking")
|
||||
output_config: Final = optional_params.get("output_config")
|
||||
thinking_enabled: Final = isinstance(thinking, dict) and thinking.get("type") == "enabled"
|
||||
effort_enabled: Final = isinstance(output_config, dict) and output_config.get("effort") is not None
|
||||
if thinking_enabled or effort_enabled:
|
||||
optional_params.pop("temperature", None)
|
||||
thinking_on: Final = isinstance(thinking, dict) and thinking.get("type") in ("enabled", "adaptive")
|
||||
effort_on: Final = (
|
||||
thinking != {"type": "disabled"}
|
||||
and isinstance(output_config, dict)
|
||||
and output_config.get("effort") is not None
|
||||
and not AnthropicModelInfo.is_adaptive_thinking_model(model, custom_llm_provider)
|
||||
)
|
||||
if not (thinking_on or effort_on):
|
||||
return
|
||||
temperature: Final = optional_params.get("temperature")
|
||||
top_p: Final = optional_params.get("top_p")
|
||||
if temperature is not None and temperature != 1:
|
||||
optional_params.pop("temperature")
|
||||
if isinstance(top_p, int | float) and top_p < _MIN_TOP_P_WITH_THINKING:
|
||||
optional_params.pop("top_p")
|
||||
optional_params.pop("top_k", None)
|
||||
|
||||
def transform_anthropic_messages_request(
|
||||
self,
|
||||
|
|
@ -616,9 +622,9 @@ class AnthropicMessagesConfig(BaseAnthropicMessagesConfig):
|
|||
custom_llm_provider=self._resolved_provider,
|
||||
)
|
||||
|
||||
self._drop_incompatible_temperature_for_thinking(
|
||||
self._drop_sampling_params_incompatible_with_thinking(
|
||||
model=model,
|
||||
optional_params=anthropic_messages_optional_request_params,
|
||||
optional_params=anthropic_messages_optional_request_params, # pyright: ignore[reportUnknownArgumentType] # override signature fixes this param as a bare dict
|
||||
custom_llm_provider=self._resolved_provider,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -248,15 +248,44 @@ def test_pinned_temperature_preserved_when_thinking_dropped():
|
|||
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)
|
||||
@pytest.mark.parametrize(
|
||||
("sampling_params", "kept"),
|
||||
[
|
||||
({"temperature": 0}, {}),
|
||||
({"temperature": 1}, {"temperature": 1}),
|
||||
({"top_k": 5}, {}),
|
||||
({"top_p": 0.5}, {}),
|
||||
({"top_p": 0.95}, {"top_p": 0.95}),
|
||||
({"top_p": 0.97}, {"top_p": 0.97}),
|
||||
({"temperature": 0, "top_k": 5, "top_p": 0.5}, {}),
|
||||
],
|
||||
)
|
||||
def test_adaptive_thinking_keeps_only_sampling_values_anthropic_accepts(sampling_params, kept):
|
||||
"""#42886: Anthropic rejects temperature other than 1, any top_k, and top_p below 0.95
|
||||
"when thinking is enabled or in adaptive mode", so adaptive thinking gets the same
|
||||
reconciliation as legacy enabled thinking while the thinking itself is preserved."""
|
||||
result = _transform("claude-sonnet-4-6", {**_claude_code_payload(effort="high"), **sampling_params})
|
||||
|
||||
assert result["thinking"] == {"type": "adaptive"}
|
||||
assert result["temperature"] == 0
|
||||
assert {key: result[key] for key in ("temperature", "top_k", "top_p") if key in result} == kept
|
||||
|
||||
|
||||
def test_sampling_params_survive_on_an_adaptive_model_without_thinking():
|
||||
"""Without thinking there is no conflict, so the caller's sampling values pass through."""
|
||||
result = _transform("claude-sonnet-4-6", {"max_tokens": 1024, "temperature": 0, "top_k": 5, "top_p": 0.5})
|
||||
|
||||
assert "thinking" not in result
|
||||
assert (result["temperature"], result["top_k"], result["top_p"]) == (0, 5, 0.5)
|
||||
|
||||
|
||||
def test_top_k_dropped_with_legacy_enabled_thinking():
|
||||
"""The legacy enabled-thinking path drops top_k too, not only a pinned temperature."""
|
||||
params = _claude_code_payload(effort="medium")
|
||||
params["top_k"] = 5
|
||||
result = _transform("claude-haiku-4-5", params)
|
||||
|
||||
assert result["thinking"]["type"] == "enabled"
|
||||
assert "top_k" not in result
|
||||
|
||||
|
||||
def test_pinned_temperature_dropped_for_opus_4_5_effort():
|
||||
|
|
@ -270,6 +299,24 @@ def test_pinned_temperature_dropped_for_opus_4_5_effort():
|
|||
assert "temperature" not in result
|
||||
|
||||
|
||||
def test_explicitly_disabled_thinking_keeps_sampling_params_for_opus_4_5_effort():
|
||||
"""Effort alone counts as thinking on Opus 4.5, but not when the caller turned
|
||||
thinking off, so their sampling values must pass through."""
|
||||
result = _transform(
|
||||
"claude-opus-4-5",
|
||||
{
|
||||
"max_tokens": 1024,
|
||||
"thinking": {"type": "disabled"},
|
||||
"output_config": {"effort": "medium"},
|
||||
"temperature": 0,
|
||||
"top_k": 5,
|
||||
"top_p": 0.5,
|
||||
},
|
||||
)
|
||||
|
||||
assert (result["temperature"], result["top_k"], result["top_p"]) == (0, 5, 0.5)
|
||||
|
||||
|
||||
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."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue