Skip to content

Commit f3f71f0

Browse files
authored
Python: fix(bedrock): don't send toolChoice when no tools are configured (microsoft#5172)
* fix(bedrock): don't send toolChoice when no tools are configured BedrockChatClient was sending toolConfig.toolChoice even when no tools were configured (tools=None). AWS Bedrock requires toolConfig.tools to be present whenever toolChoice is specified, causing a 400 validation error. Only set toolChoice when tool_config has a 'tools' key present. Fixes microsoft#5165 Signed-off-by: bahtya <[email protected]> * test: add tests for toolChoice without tools - test_prepare_options_tool_choice_auto_without_tools_omits_tool_config - test_prepare_options_tool_choice_required_without_tools_omits_tool_config Verifies that toolConfig is omitted when tool_choice is set but no tools are provided, preventing ParamValidationError from Bedrock. * fix: address maintainer feedback — remove stray test file, raise ValueError for required without tools 1. Remove test_addition.py — stray duplicate of tests already in python/packages/bedrock/tests/test_bedrock_client.py, missing all necessary imports and would fail with NameError. 2. Change tool_choice='required' handling to raise ValueError when no tools are configured instead of silently falling through. Using 'required' without tools is a logical contradiction — the model must invoke a tool but none exist — so surfacing this as a ValueError helps callers catch the misconfiguration early. 3. Update the corresponding test to expect ValueError instead of silently omitted toolConfig. --------- Signed-off-by: bahtya <[email protected]>
1 parent ddfbdf5 commit f3f71f0

2 files changed

Lines changed: 40 additions & 3 deletions

File tree

python/packages/bedrock/agent_framework_bedrock/_chat_client.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,14 @@ def _prepare_options(
413413
# Omit toolConfig entirely so the model won't attempt tool calls.
414414
tool_config = None
415415
case "auto":
416-
tool_config = tool_config or {}
417-
tool_config["toolChoice"] = {"auto": {}}
416+
if tool_config and "tools" in tool_config:
417+
tool_config["toolChoice"] = {"auto": {}}
418418
case "required":
419-
tool_config = tool_config or {}
419+
if not (tool_config and "tools" in tool_config):
420+
raise ValueError(
421+
"tool_choice='required' requires at least one tool to be configured, "
422+
"but no tools were provided."
423+
)
420424
if required_name := tool_mode.get("required_function_name"):
421425
tool_config["toolChoice"] = {"tool": {"name": required_name}}
422426
else:

python/packages/bedrock/tests/test_bedrock_client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,36 @@ def test_prepare_options_tool_choice_required_includes_any() -> None:
137137

138138
assert "toolConfig" in request
139139
assert request["toolConfig"]["toolChoice"] == {"any": {}}
140+
141+
142+
143+
def test_prepare_options_tool_choice_auto_without_tools_omits_tool_config() -> None:
144+
"""When tool_choice='auto' but no tools are provided, toolConfig must be omitted.
145+
146+
Without tools, setting toolChoice would cause a ParamValidationError from Bedrock.
147+
"""
148+
client = _make_client()
149+
messages = [Message(role="user", contents=[Content.from_text(text="hello")])]
150+
151+
options: dict[str, Any] = {
152+
"tool_choice": "auto",
153+
}
154+
155+
request = client._prepare_options(messages, options)
156+
157+
assert "toolConfig" not in request, (
158+
f"toolConfig should be omitted when no tools are provided, got: {request.get('toolConfig')}"
159+
)
160+
161+
162+
def test_prepare_options_tool_choice_required_without_tools_raises() -> None:
163+
"""When tool_choice='required' but no tools are provided, a ValueError must be raised."""
164+
client = _make_client()
165+
messages = [Message(role="user", contents=[Content.from_text(text="hello")])]
166+
167+
options: dict[str, Any] = {
168+
"tool_choice": "required",
169+
}
170+
171+
with pytest.raises(ValueError, match="tool_choice='required' requires at least one tool"):
172+
client._prepare_options(messages, options)

0 commit comments

Comments
 (0)