Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1144,14 +1144,18 @@ def _prepare_tools_for_anthropic(
request_state.tool_name_aliases = {}

# Process tool choice
allow_multiple = options.get("allow_multiple_tool_calls")
if options.get("tool_choice") is None:
# Anthropic only exposes parallel tool use through tool_choice, so carry the setting
# on its default "auto" mode (e.g. after the function loop resets tool_choice="required").
if allow_multiple is not None and result.get("tools"):
result["tool_choice"] = {"type": "auto", "disable_parallel_tool_use": not allow_multiple}
return result or None
tool_mode = validate_tool_mode(options.get("tool_choice"))
if tool_mode is None:
return result or None
if "allowed_tools" in tool_mode:
logger.warning("allowed_tools is not supported by Anthropic; the setting will be ignored")
allow_multiple = options.get("allow_multiple_tool_calls")
match tool_mode.get("mode"):
case "auto":
tool_choice: dict[str, Any] = {"type": "auto"}
Expand Down
76 changes: 76 additions & 0 deletions python/packages/anthropic/tests/test_anthropic_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,82 @@ async def test_prepare_options_with_tool_choice_auto(
assert "allow_multiple_tool_calls" not in run_options


async def test_prepare_options_allow_multiple_tool_calls_without_tool_choice(
mock_anthropic_client: MagicMock,
) -> None:
"""allow_multiple_tool_calls=False must disable parallel tool use even when tool_choice is unset."""
client = create_test_anthropic_client(mock_anthropic_client)

@tool(approval_mode="never_require")
def get_weather(city: str) -> str:
"""Get the weather."""
return f"sunny in {city}"

messages = [Message(role="user", contents=["Weather in Paris and Rome?"])]
run_options = client._prepare_options(messages, {"tools": [get_weather], "allow_multiple_tool_calls": False})

assert run_options["tool_choice"]["type"] == "auto"
assert run_options["tool_choice"]["disable_parallel_tool_use"] is True


async def test_function_loop_keeps_parallel_tool_use_disabled_after_required_tool_choice(
mock_anthropic_client: MagicMock,
) -> None:
"""After the loop resets tool_choice='required', later requests must still disable parallel tool use."""
client = create_test_anthropic_client(mock_anthropic_client)

@tool(approval_mode="never_require")
def get_weather(city: str) -> str:
"""Get the weather."""
return f"sunny in {city}"

usage = BetaUsage(input_tokens=10, output_tokens=5)
mock_anthropic_client.beta.messages.create.side_effect = [
BetaMessage(
id="msg_1",
type="message",
role="assistant",
model="claude-test",
content=[BetaToolUseBlock(type="tool_use", id="toolu_1", name="get_weather", input={"city": "Paris"})],
stop_reason="tool_use",
usage=usage,
),
BetaMessage(
id="msg_2",
type="message",
role="assistant",
model="claude-test",
content=[BetaToolUseBlock(type="tool_use", id="toolu_2", name="get_weather", input={"city": "Rome"})],
stop_reason="tool_use",
usage=usage,
),
BetaMessage(
id="msg_3",
type="message",
role="assistant",
model="claude-test",
content=[BetaTextBlock(type="text", text="Sunny in both.")],
stop_reason="end_turn",
usage=usage,
),
]

await client.get_response(
[Message(role="user", contents=["Weather in Paris and Rome?"])],
options={"tools": [get_weather], "tool_choice": "required", "allow_multiple_tool_calls": False},
)

calls = mock_anthropic_client.beta.messages.create.call_args_list
assert len(calls) == 3
assert calls[0].kwargs["tool_choice"]["type"] == "any"
assert calls[0].kwargs["tool_choice"]["disable_parallel_tool_use"] is True
for call in calls[1:]:
tool_choice = call.kwargs.get("tool_choice")
assert tool_choice is not None
assert tool_choice["type"] == "auto"
assert tool_choice["disable_parallel_tool_use"] is True


async def test_prepare_options_with_tool_choice_required(
mock_anthropic_client: MagicMock,
) -> None:
Expand Down
Loading