fix(anthropic): preserve immutable drop params

Signed-off-by: Hasnaat Hussain <hasnaat.hussain.2@gmail.com>
This commit is contained in:
Hasnaat Hussain 2026-08-12 22:27:35 +05:00
parent 13bb1b6cfc
commit 48cb0d912f
2 changed files with 62 additions and 35 deletions

View file

@ -680,20 +680,32 @@ def anthropic_messages_handler(
or kwargs.get("drop_params") is True
)
if should_drop_params:
anthropic_messages_optional_request_params = _drop_unsupported_anthropic_messages_params(
filtered_anthropic_messages_params: Final = (
_drop_unsupported_anthropic_messages_params(
anthropic_messages_optional_request_params=anthropic_messages_optional_request_params,
model=model,
custom_llm_provider=custom_llm_provider,
additional_drop_params=kwargs.get("additional_drop_params"),
)
if is_reasoning_auto_summary_enabled():
thinking_param: Final = anthropic_messages_optional_request_params.get("thinking")
if isinstance(thinking_param, dict) and thinking_param.get("type") != "disabled":
anthropic_messages_optional_request_params["thinking"] = {
**thinking_param,
"display": "summarized",
if should_drop_params
else anthropic_messages_optional_request_params
)
thinking_param: Final = filtered_anthropic_messages_params.get("thinking")
final_anthropic_messages_params: Final = (
MappingProxyType(
{
**filtered_anthropic_messages_params,
"thinking": {
**thinking_param,
"display": "summarized",
},
}
)
if is_reasoning_auto_summary_enabled()
and isinstance(thinking_param, dict)
and thinking_param.get("type") != "disabled"
else filtered_anthropic_messages_params
)
resolved_api_base: Final = (
dynamic_api_base
@ -704,7 +716,7 @@ def anthropic_messages_handler(
model=model,
messages=strip_provider_specific_fields_from_anthropic_messages(messages),
anthropic_messages_provider_config=anthropic_messages_provider_config,
anthropic_messages_optional_request_params=dict(anthropic_messages_optional_request_params),
anthropic_messages_optional_request_params=dict(final_anthropic_messages_params),
_is_async=is_async,
client=client,
custom_llm_provider=custom_llm_provider,

View file

@ -299,11 +299,15 @@ async def test_bedrock_converse_budget_tokens_preserved():
# Verify thinking parameter is passed through with budget_tokens preserved
thinking_param = call_kwargs.get("thinking")
assert thinking_param is not None, "thinking parameter should be passed to acompletion"
assert thinking_param.get("type") == "enabled", "thinking.type should be 'enabled'"
assert thinking_param.get("budget_tokens") == 1024, (
f"thinking.budget_tokens should be 1024, but got {thinking_param.get('budget_tokens')}"
)
assert (
thinking_param is not None
), "thinking parameter should be passed to acompletion"
assert (
thinking_param.get("type") == "enabled"
), "thinking.type should be 'enabled'"
assert (
thinking_param.get("budget_tokens") == 1024
), f"thinking.budget_tokens should be 1024, but got {thinking_param.get('budget_tokens')}"
def test_openai_model_with_thinking_converts_to_reasoning():
@ -335,18 +339,23 @@ def test_openai_model_with_thinking_converts_to_reasoning():
call_kwargs = mock_responses.call_args.kwargs
# Verify reasoning is set (converted from thinking)
assert "reasoning" in call_kwargs, "reasoning should be passed to litellm.responses"
assert (
"reasoning" in call_kwargs
), "reasoning should be passed to litellm.responses"
# budget_tokens=1024 -> effort="low" (at the LOW budget threshold)
# reasoning_auto_summary is False by default, so no summary key
expected_reasoning = {"effort": "low"}
assert call_kwargs["reasoning"] == expected_reasoning, (
f"reasoning should be {expected_reasoning} for budget_tokens=1024, got {call_kwargs.get('reasoning')}"
f"reasoning should be {expected_reasoning} for budget_tokens=1024, "
f"got {call_kwargs.get('reasoning')}"
)
assert "summary" not in call_kwargs["reasoning"]
# Verify thinking is NOT passed directly to the Responses API
assert "thinking" not in call_kwargs, "thinking should NOT be passed directly to litellm.responses"
assert (
"thinking" not in call_kwargs
), "thinking should NOT be passed directly to litellm.responses"
class TestThinkingParameterTransformation:
@ -399,7 +408,9 @@ class TestThinkingParameterTransformation:
thinking=thinking,
model="openai/gpt-5.2",
)
assert result == {"reasoning_effort": {"effort": "high", "summary": "detailed"}}
assert result == {
"reasoning_effort": {"effort": "high", "summary": "detailed"}
}
finally:
litellm.reasoning_auto_summary = original
@ -597,9 +608,9 @@ class TestThinkingSummaryPreservation:
mock_responses.assert_called_once()
call_kwargs = mock_responses.call_args.kwargs
reasoning = call_kwargs["reasoning"]
assert reasoning["summary"] == "concise", (
f"Expected summary='concise', got summary='{reasoning.get('summary')}'"
)
assert (
reasoning["summary"] == "concise"
), f"Expected summary='concise', got summary='{reasoning.get('summary')}'"
def test_responses_adapter_preserves_summary(self):
"""translate_thinking_to_reasoning should include summary when user provides it."""
@ -608,7 +619,9 @@ class TestThinkingSummaryPreservation:
)
thinking = {"type": "enabled", "budget_tokens": 5000, "summary": "concise"}
result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(thinking)
result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(
thinking
)
assert result == {"effort": "high", "summary": "concise"}
def test_responses_adapter_no_summary_by_default(self):
@ -622,7 +635,11 @@ class TestThinkingSummaryPreservation:
try:
litellm.reasoning_auto_summary = False
thinking = {"type": "enabled", "budget_tokens": 5000}
result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(thinking)
result = (
LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(
thinking
)
)
assert result == {"effort": "high"}
assert result is not None and "summary" not in result
finally:
@ -639,7 +656,9 @@ class TestThinkingSummaryPreservation:
thinking=thinking,
model="openai/gpt-5.2",
)
assert result == {"reasoning_effort": {"effort": "high", "summary": "concise"}}
assert result == {
"reasoning_effort": {"effort": "high", "summary": "concise"}
}
def test_translate_thinking_for_model_disabled_stays_plain_string_when_auto_summary_enabled(self):
"""Disabled thinking must stay a plain string even when reasoning_auto_summary is on."""
@ -785,7 +804,9 @@ def test_presanitized_flag_not_leaked_to_provider_params():
def fake_base_handler(*args, **kwargs):
captured.update(kwargs)
captured["optional"] = kwargs.get("anthropic_messages_optional_request_params", {})
captured["optional"] = kwargs.get(
"anthropic_messages_optional_request_params", {}
)
return "stub"
with patch.object(
@ -1686,18 +1707,12 @@ async def test_anthropic_messages_forwards_safeguards_and_dangerous_tool_use_bet
assert response["safeguard_results"] == safeguard_results
@pytest.mark.asyncio
async def test_anthropic_pass_through_drop_params(monkeypatch):
from unittest.mock import AsyncMock
from litellm.llms.anthropic.experimental_pass_through.messages.handler import (
anthropic_messages_handler,
)
mock_handler = AsyncMock()
from litellm.llms.anthropic.experimental_pass_through.messages import handler
mock_handler = AsyncMock()
monkeypatch.setattr(handler.base_llm_http_handler, "anthropic_messages_handler", mock_handler)
# Test automatic dropping of context_management for haiku on vertex_ai
await anthropic_messages_handler(
await handler.anthropic_messages_handler(
model="vertex_ai/claude-3-haiku-20240307",
messages=[{"role": "user", "content": "hello"}],
max_tokens=64,
@ -1706,6 +1721,6 @@ async def test_anthropic_pass_through_drop_params(monkeypatch):
custom_llm_provider="vertex_ai",
is_async=True,
)
called_kwargs = mock_handler.call_args.kwargs
optional_params = called_kwargs.get("anthropic_messages_optional_request_params", {})
optional_params = mock_handler.call_args.kwargs.get("anthropic_messages_optional_request_params", {})
assert "context_management" not in optional_params