diff --git a/studio/backend/routes/inference.py b/studio/backend/routes/inference.py index 0685f4bb04..afd1a56297 100644 --- a/studio/backend/routes/inference.py +++ b/studio/backend/routes/inference.py @@ -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, ) diff --git a/studio/backend/tests/test_sampling_params_routing.py b/studio/backend/tests/test_sampling_params_routing.py index 736695e926..431ff4cd65 100644 --- a/studio/backend/tests/test_sampling_params_routing.py +++ b/studio/backend/tests/test_sampling_params_routing.py @@ -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