Forward disable_parallel_tool_use through Anthropic client-tool passthrough (PR #5711)

Round 11 reviewer consensus (10/10): the `disable_parallel_tool_use`
translation added in round 11b reached the Anthropic-compat server-tool
GGUF loop but not the analogous client-tool passthrough branch. A
client sending `/v1/messages` with custom tools plus
`tool_choice: {"type":"auto","disable_parallel_tool_use":true}` therefore
took the passthrough branch with the opt-out silently dropped on the
llama-server `/v1/chat/completions` body.

Thread the translated `anthropic_parallel_tool_calls` value through
`_anthropic_passthrough_stream` and `_anthropic_passthrough_non_streaming`
into the shared `_build_passthrough_payload`, which already knows the
field. Test pins both helpers' signatures and that the field reaches
the body via the payload builder.
This commit is contained in:
Daniel Han 2026-05-24 17:18:33 +00:00
commit 48df6a98c8
2 changed files with 43 additions and 0 deletions

View file

@ -4575,6 +4575,7 @@ async def anthropic_messages(
repetition_penalty = repetition_penalty,
presence_penalty = presence_penalty,
tool_choice = openai_tool_choice,
parallel_tool_calls = anthropic_parallel_tool_calls,
session_id = payload.session_id,
cancel_id = payload.cancel_id,
)
@ -4593,6 +4594,7 @@ async def anthropic_messages(
repetition_penalty = repetition_penalty,
presence_penalty = presence_penalty,
tool_choice = openai_tool_choice,
parallel_tool_calls = anthropic_parallel_tool_calls,
)
if server_tools:
@ -5010,6 +5012,7 @@ async def _anthropic_passthrough_stream(
repetition_penalty = None,
presence_penalty = None,
tool_choice = "auto",
parallel_tool_calls = None,
session_id = None,
cancel_id = None,
):
@ -5028,6 +5031,7 @@ async def _anthropic_passthrough_stream(
min_p = min_p,
repetition_penalty = repetition_penalty,
presence_penalty = presence_penalty,
parallel_tool_calls = parallel_tool_calls,
tool_choice = tool_choice,
backend_ctx = llama_backend.context_length,
)
@ -5162,6 +5166,7 @@ async def _anthropic_passthrough_non_streaming(
repetition_penalty = None,
presence_penalty = None,
tool_choice = "auto",
parallel_tool_calls = None,
):
"""Non-streaming client-side pass-through."""
target_url = f"{llama_backend.base_url}/v1/chat/completions"
@ -5177,6 +5182,7 @@ async def _anthropic_passthrough_non_streaming(
min_p = min_p,
repetition_penalty = repetition_penalty,
presence_penalty = presence_penalty,
parallel_tool_calls = parallel_tool_calls,
tool_choice = tool_choice,
backend_ctx = llama_backend.context_length,
)

View file

@ -874,3 +874,40 @@ def test_local_anthropic_disable_parallel_tool_use_translation():
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
def test_local_anthropic_passthrough_helpers_accept_parallel_tool_calls():
"""The Anthropic-compat client-tool passthrough helpers
(`_anthropic_passthrough_stream` /
`_anthropic_passthrough_non_streaming`) must accept and forward
`parallel_tool_calls` through `_build_passthrough_payload` so the
`disable_parallel_tool_use` translation works on the client-tool
branch the same way it does on the server-tool loop. Verified by
introspecting the signatures and confirming the field reaches the
body via the shared payload builder."""
import inspect
from routes import inference as route_mod
for fn in (
route_mod._anthropic_passthrough_stream,
route_mod._anthropic_passthrough_non_streaming,
):
params = inspect.signature(fn).parameters
assert "parallel_tool_calls" in params, (
f"{fn.__name__} must accept parallel_tool_calls so the "
"Anthropic disable_parallel_tool_use translation reaches "
"the llama-server body on the client-tool branch"
)
body = route_mod._build_passthrough_payload(
openai_messages = [{"role": "user", "content": "hi"}],
openai_tools = [{"type": "function", "function": {"name": "x"}}],
temperature = 0.7,
top_p = 0.95,
top_k = 20,
max_tokens = 64,
stream = True,
parallel_tool_calls = False,
)
assert body.get("parallel_tool_calls") is False, body