mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-15 04:19:08 +00:00
fix(anthropic): preserve immutable drop params
Signed-off-by: Hasnaat Hussain <hasnaat.hussain.2@gmail.com>
This commit is contained in:
parent
13bb1b6cfc
commit
48cb0d912f
2 changed files with 62 additions and 35 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue