-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Assignees
Labels
Type
Projects
Status