Local /v1/messages: invert disable_parallel_tool_use into parallel_tool_calls (PR #5711)
Anthropic Messages API nests `disable_parallel_tool_use` inside the `tool_choice` object (per docs.claude.com/parallel-tool-use). The local Anthropic-compat endpoint dropped that flag because the OpenAI shape it translates into uses a different name and lives at the top level instead. SDK clients (anthropic-python, anthropic-sdk-go, etc.) that already speak this dialect therefore could not opt out of parallel tool calls against the local GGUF model. Extract `disable_parallel_tool_use` from the incoming tool_choice and invert it to `parallel_tool_calls` on the agentic-loop call. Plain-chat and existing tool_choice shapes are untouched. Added a focused unit test that pins the dict/None/bool/string boundary cases.
This commit is contained in:
parent
d7a09d975b
commit
ad36aaa71d
2 changed files with 36 additions and 0 deletions
|
|
@ -4461,6 +4461,16 @@ async def anthropic_messages(
|
|||
if openai_tool_choice is None:
|
||||
openai_tool_choice = "auto"
|
||||
|
||||
# Anthropic nests `disable_parallel_tool_use` inside `tool_choice`
|
||||
# (https://docs.claude.com/en/docs/agents-and-tools/tool-use/implement-tool-use).
|
||||
# Flip it into the OpenAI-shaped `parallel_tool_calls` toggle so the
|
||||
# local GGUF tool loop respects clients that opt out of parallel calls.
|
||||
anthropic_parallel_tool_calls: Optional[bool] = None
|
||||
if isinstance(payload.tool_choice, dict):
|
||||
_disable = payload.tool_choice.get("disable_parallel_tool_use")
|
||||
if isinstance(_disable, bool):
|
||||
anthropic_parallel_tool_calls = not _disable
|
||||
|
||||
cancel_event = threading.Event()
|
||||
|
||||
# ── Tool routing ──────────────────────────────────────────
|
||||
|
|
@ -4666,6 +4676,7 @@ async def anthropic_messages(
|
|||
auto_heal_tool_calls = True,
|
||||
tool_call_timeout = 300,
|
||||
session_id = payload.session_id,
|
||||
parallel_tool_calls = anthropic_parallel_tool_calls,
|
||||
)
|
||||
|
||||
if payload.stream:
|
||||
|
|
|
|||
|
|
@ -819,3 +819,28 @@ def test_chat_settings_payload_accepts_new_sampling_keys():
|
|||
assert ip.stop == ["END"]
|
||||
assert ip.serviceTier == "standard_only"
|
||||
assert ip.parallelToolCalls is False
|
||||
|
||||
|
||||
# ── Local /v1/messages: disable_parallel_tool_use translation ──────────
|
||||
|
||||
|
||||
def test_local_anthropic_disable_parallel_tool_use_translation():
|
||||
"""Anthropic nests `disable_parallel_tool_use` under `tool_choice`
|
||||
(per docs.claude.com). The local /v1/messages GGUF tool path must
|
||||
invert it into OpenAI-shaped `parallel_tool_calls` so third-party
|
||||
clients (Claude SDK, LiteLLM in passthrough mode) opt out of
|
||||
parallel calls successfully even on the local model."""
|
||||
# Mirror the extraction logic in routes/inference.py:anthropic_messages.
|
||||
def _extract(tc):
|
||||
if isinstance(tc, dict):
|
||||
v = tc.get("disable_parallel_tool_use")
|
||||
if isinstance(v, bool):
|
||||
return not v
|
||||
return None
|
||||
|
||||
assert _extract({"type": "auto", "disable_parallel_tool_use": True}) is False
|
||||
assert _extract({"type": "any", "disable_parallel_tool_use": False}) is True
|
||||
assert _extract({"type": "auto"}) is None
|
||||
assert _extract(None) is None
|
||||
assert _extract("auto") is None # string form (non-dict) → no opinion
|
||||
assert _extract({"type": "auto", "disable_parallel_tool_use": "yes"}) is None
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue