fix(bedrock): send only the effort from output_config to Converse Claude without thinking

Other output_config keys such as task_budget never reached Converse without a
thinking block, and Bedrock answers 400 to them, so they stay behind there
This commit is contained in:
mateo-berri 2026-11-03 23:03:53 -08:00
parent afb8eb7dfe
commit ce02ebfbd0
2 changed files with 35 additions and 3 deletions

View file

@ -1222,7 +1222,9 @@ class LiteLLMAnthropicMessagesAdapter:
Bedrock Converse takes the tier in ``output_config`` with or without a ``thinking`` block, so
it travels on its own instead of only alongside ``thinking``. ``format`` stays behind, since
``_translate_output_format_to_openai`` carries it as ``response_format``.
``_translate_output_format_to_openai`` carries it as ``response_format``. Without ``thinking``
only ``effort`` goes, since the other keys (``task_budget``) never reached Converse there and
it rejects them.
"""
if "output_config" not in anthropic_message_request or not self._is_bedrock_claude_target(new_kwargs["model"]):
return
@ -1231,13 +1233,16 @@ class LiteLLMAnthropicMessagesAdapter:
if not isinstance(output_config, dict):
return
effort_config: Final = {k: v for k, v in output_config.items() if k != "format"}
has_thinking: Final = bool(anthropic_message_request.get("thinking"))
effort_config: Final = {
k: v for k, v in output_config.items() if k == "effort" or (has_thinking and k != "format")
}
if not effort_config:
return
model: Final = new_kwargs["model"]
if (
drop_params
and not anthropic_message_request.get("thinking")
and not has_thinking
and is_bedrock_application_inference_profile_arn(bedrock_capability_model(model, base_model))
):
litellm.verbose_logger.warning(UNRESOLVED_ARN_EFFORT_WITHOUT_THINKING_WARNING, model)

View file

@ -5526,6 +5526,33 @@ async def test_bedrock_converse_claude_gets_the_effort_without_a_thinking_block(
assert "thinking" not in sent
_TASK_BUDGET: Final = {"type": "tokens", "total": 20000}
@pytest.mark.asyncio
@pytest.mark.parametrize(
("output_config", "sent_output_config"),
[
pytest.param({"effort": "low", "task_budget": _TASK_BUDGET}, {"effort": "low"}, id="effort_and_task_budget"),
pytest.param({"task_budget": _TASK_BUDGET}, None, id="task_budget_only"),
],
)
async def test_bedrock_converse_claude_without_a_thinking_block_gets_only_the_effort_from_output_config(
_converse_route: respx.Route,
output_config: Mapping[str, object],
sent_output_config: Mapping[str, object] | None,
) -> None:
await litellm.anthropic.messages.acreate(
model=f"bedrock/converse/{_CONVERSE_EFFORT_MODEL}",
max_tokens=64,
messages=[{"role": "user", "content": "Name the capital of France."}],
output_config=output_config,
drop_params=True,
)
assert _sent_converse_fields(_converse_route).get("output_config") == sent_output_config
@pytest.mark.asyncio
@pytest.mark.parametrize(
("drop_params_source", "forwards_effort"),