Skip to content

Python: BedrockChatClient sends invalid toolChoice: {"none": {}} — Bedrock rejects it #4529

@RomanMart

Description

@RomanMart

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

  1. Create an agent with tools using BedrockChatClient
  2. Let the agent's function-invocation loop (FunctionInvocationLayer) exhaust its max iterations or hit the error threshold
  3. The framework sets tool_choice="none" for a final "wrap-up" LLM call (to get a plain-text summary)
  4. _prepare_options translates this to {"none": {}} and includes it in toolConfig.toolChoice
  5. 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.0b260304
  • agent-framework==1.0.0rc3
  • Python 3.13
  • AWS region: eu-central-1
  • Model: eu.anthropic.claude-sonnet-4-20250514-v1:0

Metadata

Metadata

Labels

model clientsIssues related to the model client implementationspythonv1.0Features being tracked for the version 1.0 GA

Type

Projects

Status

Community PR

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions