Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions litellm/llms/anthropic/chat/transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1088,24 +1088,29 @@ def map_openai_params( # noqa: PLR0915
elif param == "thinking":
optional_params["thinking"] = value
elif param == "reasoning_effort" and isinstance(value, str):
optional_params["thinking"] = AnthropicConfig._map_reasoning_effort(
mapped_thinking = AnthropicConfig._map_reasoning_effort(
reasoning_effort=value, model=model
)
# For Claude 4.6+ models, effort is controlled via output_config,
# not thinking budget_tokens. Map reasoning_effort to output_config.
if AnthropicConfig._is_claude_4_6_model(
model
) or AnthropicConfig._is_claude_4_7_model(model):
effort_map = {
"low": "low",
"minimal": "low",
"medium": "medium",
"high": "high",
"xhigh": "xhigh",
"max": "max",
}
mapped_effort = effort_map.get(value, value)
optional_params["output_config"] = {"effort": mapped_effort}
if mapped_thinking is None:
optional_params.pop("thinking", None)
optional_params.pop("output_config", None)
else:
optional_params["thinking"] = mapped_thinking
# For Claude 4.6+ models, effort is controlled via output_config,
# not thinking budget_tokens. Map reasoning_effort to output_config.
if AnthropicConfig._is_claude_4_6_model(
model
) or AnthropicConfig._is_claude_4_7_model(model):
effort_map = {
"low": "low",
"minimal": "low",
"medium": "medium",
"high": "high",
"xhigh": "xhigh",
"max": "max",
}
mapped_effort = effort_map.get(value, value)
optional_params["output_config"] = {"effort": mapped_effort}
elif param == "web_search_options" and isinstance(value, dict):
hosted_web_search_tool = self.map_web_search_tool(
cast(OpenAIWebSearchOptions, value)
Expand Down
6 changes: 5 additions & 1 deletion litellm/llms/bedrock/chat/converse_transformation.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,9 +449,13 @@ def _handle_reasoning_effort_parameter(
optional_params.update(reasoning_config)
else:
# Anthropic and other models: convert to thinking parameter
optional_params["thinking"] = AnthropicConfig._map_reasoning_effort(
mapped_thinking = AnthropicConfig._map_reasoning_effort(
reasoning_effort=reasoning_effort, model=model
)
if mapped_thinking is None:
optional_params.pop("thinking", None)
else:
optional_params["thinking"] = mapped_thinking

@staticmethod
def _clamp_thinking_budget_tokens(optional_params: dict) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1687,9 +1687,7 @@ def test_max_effort_rejected_for_opus_45():

messages = [{"role": "user", "content": "Test"}]

with pytest.raises(
ValueError, match="effort='max' is not supported by this model"
):
with pytest.raises(ValueError, match="effort='max' is not supported by this model"):
optional_params = {"output_config": {"effort": "max"}}
config.transform_request(
model="claude-opus-4-5-20251101",
Expand Down Expand Up @@ -2251,9 +2249,7 @@ def test_max_effort_rejected_for_sonnet_46():
config = AnthropicConfig()
messages = [{"role": "user", "content": "Test"}]

with pytest.raises(
ValueError, match="effort='max' is not supported by this model"
):
with pytest.raises(ValueError, match="effort='max' is not supported by this model"):
config.transform_request(
model="claude-sonnet-4-6-20260219",
messages=messages,
Expand Down Expand Up @@ -2315,6 +2311,30 @@ def test_effort_beta_header_not_injected_for_46_models():
assert result is False, f"is_effort_used should return False for {model}"


@pytest.mark.parametrize(
"model",
[
"claude-opus-4-5-20251101",
"claude-opus-4-6-20250514",
"claude-sonnet-4-6-20260219",
"claude-opus-4-7",
],
)
def test_reasoning_effort_none_omits_thinking_and_output_config(model):
"""reasoning_effort="none" must omit thinking and output_config from the request."""
config = AnthropicConfig()

result = config.map_openai_params(
non_default_params={"reasoning_effort": "none"},
optional_params={},
model=model,
drop_params=False,
)

assert "thinking" not in result
assert "output_config" not in result


def test_effort_beta_header_still_injected_for_older_models():
"""
Test that is_effort_used still returns True for pre-4.6 models
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,28 @@ def test_reasoning_with_forced_tool_choice_switches_to_auto():
assert optional_params["tool_choice"] == {"auto": {}}


@pytest.mark.parametrize(
"model",
[
"bedrock/converse/us.anthropic.claude-opus-4-5-20251101-v1:0",
"bedrock/converse/us.anthropic.claude-opus-4-6-v1",
"bedrock/converse/us.anthropic.claude-opus-4-7",
],
)
def test_reasoning_effort_none_omits_thinking_for_anthropic_converse(model):
"""reasoning_effort="none" must omit thinking from the Bedrock Converse request."""
config = AmazonConverseConfig()

optional_params = config.map_openai_params(
non_default_params={"reasoning_effort": "none"},
optional_params={},
model=model,
drop_params=False,
)

assert "thinking" not in optional_params


def test_get_supported_openai_params():
config = AmazonConverseConfig()
supported_params = config.get_supported_openai_params(
Expand Down
Loading