Repository navigation
Description
Bug Description
BedrockChatClient._prepare_options() maps tool_choice="none" to toolConfig.toolChoice: {"none": {}}, but the AWS Bedrock Converse / ConverseStream API only accepts auto, any, or tool as valid toolChoice keys. This causes a botocore.exceptions.ParamValidationError at runtime.
Steps to Reproduce
- Create an agent with tools using
BedrockChatClient - Let the agent's function-invocation loop (
FunctionInvocationLayer) exhaust its max iterations or hit the error threshold - The framework sets
tool_choice="none"for a final "wrap-up" LLM call (to get a plain-text summary) _prepare_optionstranslates this to{"none": {}}and includes it intoolConfig.toolChoice- Bedrock rejects the request
Error
botocore.exceptions.ParamValidationError: Parameter validation failed:
Unknown parameter in toolConfig.toolChoice: "none", must be one of: auto, any, tool
Root Cause
In agent_framework_bedrock/_chat_client.py (line ~395), the match statement treats "none" the same as "auto":
match tool_mode.get("mode"):
case "auto" | "none":
tool_config["toolChoice"] = {tool_mode.get("mode"): {}}This produces {"none": {}} which is not a valid Bedrock API parameter.
The "none" mode is set by FunctionInvocationLayer in agent_framework/_tools.py in several places (lines ~2183, 2192, 2221, 2268, 2321, 2360) — typically when max iterations are exhausted or error thresholds are reached, forcing a final non-tool call.
Expected Behavior
When tool_choice="none", the Bedrock client should omit toolConfig entirely from the request (or at minimum omit the toolChoice key), since "none" semantically means "don't use tools on this call." This is how the OpenAI client handles it — by stripping tool_choice when there are no tools.
Suggested Fix
if tool_mode := validate_tool_mode(options.get("tool_choice")):
match tool_mode.get("mode"):
case "none":
# Bedrock doesn't support toolChoice "none".
# Omit toolConfig entirely so the model won't call tools.
tool_config = None
case "auto":
tool_config = tool_config or {}
tool_config["toolChoice"] = {"auto": {}}
case "required":
tool_config = tool_config or {}
if required_name := tool_mode.get("required_function_name"):
tool_config["toolChoice"] = {"tool": {"name": required_name}}
else:
tool_config["toolChoice"] = {"any": {}}
case _:
raise ValueError(f"Unsupported tool mode for Bedrock: {tool_mode.get('mode')}")Workaround
Subclass BedrockChatClient and override _prepare_options to intercept the "none" mode:
class PatchedBedrockChatClient(BedrockChatClient):
def _prepare_options(self, messages, options, **kwargs):
tc = options.get("tool_choice")
if tc == "none" or (isinstance(tc, dict) and tc.get("mode") == "none"):
options = {k: v for k, v in options.items() if k not in ("tool_choice", "tools")}
return super()._prepare_options(messages, options, **kwargs)Environment
agent-framework-bedrock==1.0.0b260304agent-framework==1.0.0rc3- Python 3.13
- AWS region: eu-central-1
- Model:
eu.anthropic.claude-sonnet-4-20250514-v1:0
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
LEDazzio01 commentedon Mar 6, 2026
I've submitted a fix for this in PR #4535.
Root cause:
_prepare_optionshadcase "auto" | "none"which producedtoolConfig.toolChoice: {"none": {}}— an invalid Bedrock API parameter.Fix: Split the match branch so
"none"setstool_config = None, omittingtoolConfigentirely from the request. This matches the semantic intent ("don't use tools") and is consistent with how the OpenAI client handles it.Added 3 unit tests covering
none,auto, andrequiredtool_choice modes.fix: omit toolConfig when tool_choice="none" in BedrockChatClient (#4535