Merge pull request #35738 from BerriAI/litellm_bedrock_tool_choice_parallel_conflict

fix(bedrock): drop conflicting tool_choice.type when toolConfig.toolChoice is set
This commit is contained in:
Mateo Wang 2026-08-04 14:30:10 -07:00 • committed by GitHub
commit 27885076e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 87 additions and 0 deletions

View file

@ -1213,6 +1213,20 @@ class AmazonConverseConfig(BaseConfig):
}
return {**additional_request_params, **merged_entries}
@staticmethod
def _drop_tool_choice_type_conflicting_with_tool_config(additional_request_params: dict) -> None:
"""Drop ``tool_choice.type`` from the Anthropic passthrough fields.
Converse rejects a request carrying both ``toolConfig.toolChoice`` and an
``additionalModelRequestFields.tool_choice.type``, so once the caller asked for a
tool choice the type has to come from ``toolChoice`` alone. Sibling keys such as
``disable_parallel_tool_use`` have no ``toolConfig`` equivalent and are accepted
alongside ``toolChoice``, so they stay.
"""
tool_choice = additional_request_params.get("tool_choice")
if isinstance(tool_choice, dict):
tool_choice.pop("type", None)
def _prepare_request_params(
self, optional_params: dict, model: str, drop_params: bool = False
) -> tuple[dict, dict, dict, OutputConfigBlock | None]:
@ -1569,6 +1583,7 @@ class AmazonConverseConfig(BaseConfig):
)
if tool_choice_values is not None:
bedrock_tool_config["toolChoice"] = tool_choice_values
self._drop_tool_choice_type_conflicting_with_tool_config(additional_request_params)
data: Final[CommonRequestObject] = {
"inferenceConfig": self._transform_inference_params(inference_params=inference_params),

View file

@ -4263,6 +4263,78 @@ def test_parallel_tool_calls_emits_typed_auto_tool_choice(parallel_tool_calls, e
}
@pytest.mark.parametrize(
"tool_choice, expected_tool_config_choice",
[
("auto", {"auto": {}}),
("required", {"any": {}}),
({"type": "function", "function": {"name": "get_current_weather"}}, {"tool": {"name": "get_current_weather"}}),
],
)
def test_parallel_tool_calls_with_explicit_tool_choice_omits_conflicting_type(tool_choice, expected_tool_config_choice):
config = AmazonConverseConfig()
model = "us.anthropic.claude-opus-4-8"
messages = [{"role": "user", "content": "What's the weather in SF and NYC?"}]
optional_params = config.map_openai_params(
non_default_params={"parallel_tool_calls": False, "tool_choice": tool_choice, "tools": _TOOL_PARAM},
optional_params={},
model=model,
drop_params=False,
)
request_data = config.transform_request(
model=model,
messages=messages,
optional_params=optional_params,
litellm_params={},
headers={},
)
assert request_data["toolConfig"]["toolChoice"] == expected_tool_config_choice
assert request_data["additionalModelRequestFields"]["tool_choice"] == {"disable_parallel_tool_use": True}
def test_tool_choice_type_kept_when_no_tool_config_choice_conflicts():
config = AmazonConverseConfig()
model = "us.anthropic.claude-opus-4-8"
optional_params = config.map_openai_params(
non_default_params={"parallel_tool_calls": False, "tools": _TOOL_PARAM},
optional_params={},
model=model,
drop_params=False,
)
request_data = config.transform_request(
model=model,
messages=[{"role": "user", "content": "What's the weather in SF and NYC?"}],
optional_params=optional_params,
litellm_params={},
headers={},
)
assert "toolChoice" not in request_data["toolConfig"]
assert request_data["additionalModelRequestFields"]["tool_choice"] == {
"type": "auto",
"disable_parallel_tool_use": True,
}
def test_drop_tool_choice_type_leaves_other_passthrough_fields_untouched():
additional_request_params = {
"tool_choice": {"type": "tool", "name": "get_weather", "disable_parallel_tool_use": True},
"anthropic_beta": ["some-beta"],
}
AmazonConverseConfig._drop_tool_choice_type_conflicting_with_tool_config(additional_request_params)
assert additional_request_params == {
"tool_choice": {"name": "get_weather", "disable_parallel_tool_use": True},
"anthropic_beta": ["some-beta"],
}
def test_parallel_tool_use_merge_preserves_user_tool_choice_type():
merged = AmazonConverseConfig._merge_parallel_tool_use_config(
{"tool_choice": {"type": "tool", "name": "get_weather", "disable_parallel_tool_use": False}},