fix(passthrough): keep adaptive thinking/effort on native /v1/messages path (#40890)

This commit is contained in:
ege-arhan 2026-10-24 22:19:19 +00:00
parent 29edcee9eb
commit 144cadea70
2 changed files with 65 additions and 6 deletions

View file

@ -1,4 +1,4 @@
from typing import Final
from typing import Any, Final
import litellm
from litellm.litellm_core_utils.url_utils import join_api_base
@ -41,7 +41,7 @@ class OpenAILikeAnthropicMessagesConfig(AnthropicMessagesConfig):
self,
headers: dict[str, str],
model: str,
messages: list[object],
messages: list[Any],
optional_params: dict,
litellm_params: dict,
api_key: str | None = None,
@ -87,17 +87,32 @@ class OpenAILikeAnthropicMessagesConfig(AnthropicMessagesConfig):
on Anthropic-only ``cache_control`` extensions (``cache_control.ttl: 1h
is not supported``), so unless the provider declares ttl support the
hints are reduced to their portable ``{"type": ...}`` core.
The deployment speaks the Messages API natively, so caller-supplied
``thinking``/``output_config``/``temperature`` are forwarded verbatim:
the base capability checks resolve the provider as "anthropic" and
would otherwise strip modern fields (e.g. adaptive thinking) from a
model the cost map does not know (see #40890). Pop them before the
base transform so no drop warning fires, restore afterwards.
"""
request: Final = super().transform_anthropic_messages_request(
params: Final[dict[str, object]] = anthropic_messages_optional_request_params # mutable-ok: popped below
stashed: Final[dict[str, object]] = {} # mutable-ok: filled key-by-key, then restored onto the request
for _key in ("thinking", "output_config", "temperature"):
if _key in params:
stashed[_key] = params.pop(_key)
request: Final[dict[str, object]] = super().transform_anthropic_messages_request( # pyright: ignore[reportUnknownMemberType, reportUnknownVariableType] # the shared base returns a bare dict # mutable-ok: stash restored into it before returning
model=model,
messages=messages,
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
anthropic_messages_optional_request_params=params,
litellm_params=litellm_params,
headers=headers,
)
if self.supports_cache_control_ttl():
request.update(stashed)
return request
return normalize_cache_control_in_anthropic_payload(request)
normalized: Final = normalize_cache_control_in_anthropic_payload(request)
normalized.update(stashed)
return normalized
def get_complete_url(
self,
@ -151,7 +166,7 @@ class JSONProviderAnthropicMessagesConfig(OpenAILikeAnthropicMessagesConfig):
self,
headers: dict[str, str],
model: str,
messages: list[object],
messages: list[Any],
optional_params: dict,
litellm_params: dict,
api_key: str | None = None,

View file

@ -539,3 +539,47 @@ def test_safeguards_reach_an_openai_like_host_with_only_the_client_betas(
api_base="https://host/v1",
)
assert headers.get("anthropic-beta") == expected_beta, headers
def test_passthrough_keeps_adaptive_thinking_and_effort(
config: OpenAILikeAnthropicMessagesConfig, caplog: pytest.LogCaptureFixture
) -> None:
"""Regression #40890: native /v1/messages passthrough must forward
thinking/output_config/temperature verbatim, not strip them via cost-map
capability checks for a model the cost map does not know."""
import logging
with caplog.at_level(logging.WARNING):
payload = config.transform_anthropic_messages_request(
model="my-model",
messages=[{"role": "user", "content": "hi"}],
anthropic_messages_optional_request_params={
"max_tokens": 4096,
"thinking": {"type": "adaptive"},
"output_config": {"effort": "high"},
"temperature": 0.5,
},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert payload["thinking"] == {"type": "adaptive"}
assert payload["output_config"] == {"effort": "high"}
assert payload["temperature"] == 0.5
assert "Dropping adaptive" not in caplog.text
def test_passthrough_keeps_legacy_thinking_shape(
config: OpenAILikeAnthropicMessagesConfig,
) -> None:
payload = config.transform_anthropic_messages_request(
model="my-model",
messages=[{"role": "user", "content": "hi"}],
anthropic_messages_optional_request_params={
"max_tokens": 4096,
"thinking": {"type": "enabled", "budget_tokens": 1024},
},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert payload["thinking"] == {"type": "enabled", "budget_tokens": 1024}
assert payload["max_tokens"] == 4096