Require native Responses tool calls

Default Responses turns with Agent Zero function tools to a required, single native call while preserving explicit caller overrides. Keep parallel work inside Agent Zero's parallel tool so complete tool roots reach the parser deterministically.

Alessandro committed Jul 30, 2026 at 15:49 UTC ff596e2a84727b571ac5b30e877e5c432760589e
3 files changed +27
helpers/litellm_transport.py
+4
@@ -751,6 +751,10 @@ class ResponsesTransport:
751 elif "tool_choice" in request:
752 request["tool_choice"] = cls.tool_choice_from_chat(request["tool_choice"])
753
754 + if _has_tools(response_function_tools):
755 + request.setdefault("tool_choice", "required")
756 + request.setdefault("parallel_tool_calls", False)
757 +
758 if not _has_tools(request.get("tools")):
759 request.pop("tool_choice", None)
760 request.pop("parallel_tool_calls", None)
helpers/litellm_transport.py.dox.md
+1
@@ -25,6 +25,7 @@
25 - Keep provider selection and provider-specific defaults outside this helper; callers pass a resolved LiteLLM model name and kwargs.
26 - Strip Agent Zero internal kwargs before sending requests to LiteLLM.
27 - Do not send orphan tool controls when no tools are present; strict OpenAI-compatible servers can reject empty `tools` arrays.
28 +- When Agent Zero function tools are present, default Responses requests to one required native call; explicit request-level `tool_choice` and `parallel_tool_calls` values still win.
29 - Normalize function tool parameter schemas with an explicit object `properties` field before Responses requests so OpenAI-compatible chat backends reached through LiteLLM can validate them.
30 - Prefer Responses API when configured, but fallback to Chat Completions when the provider does not support Responses.
31 - Fall back to Chat Completions when a Responses request is rejected before any output by an endpoint-specific or shape-specific Bad Request indicating the provider cannot parse Responses payloads.
tests/test_stream_tool_early_stop.py
+22
@@ -1054,6 +1054,28 @@ def test_responses_request_normalizes_function_tool_parameter_shapes():
1054 },
1055 {"type": "web_search"},
1056 ]
1057 + assert request["tool_choice"] == "required"
1058 + assert request["parallel_tool_calls"] is False
1059 +
1060 +
1061 +def test_responses_request_preserves_explicit_a0_tool_controls():
1062 + request = litellm_transport.ResponsesTransport.from_chat(
1063 + [],
1064 + {
1065 + "a0_responses_function_tools": [
1066 + {
1067 + "type": "function",
1068 + "name": "native_noop",
1069 + "parameters": {"type": "object"},
1070 + }
1071 + ],
1072 + "tool_choice": "auto",
1073 + "parallel_tool_calls": True,
1074 + },
1075 + )
1076 +
1077 + assert request["tool_choice"] == "auto"
1078 + assert request["parallel_tool_calls"] is True
1079
1080
1081 def test_chat_completions_kwargs_omit_empty_tools():