Skip to content

Commit 114c537

Browse files
hzhuaNorth Coder
andcommitted
Backport default parallel_tool_calls for Responses API
Co-Authored-By: North Coder <noreply@qijizhifeng.com>
1 parent 41a45fb commit 114c537

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

nexau/archs/main_sub/execution/llm_caller.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,6 +834,16 @@ def call_llm(api_kwargs: dict[str, Any]) -> ChatCompletion:
834834
return ModelResponse.from_openai_message(response_message, usage=usage)
835835

836836

837+
def _default_openai_responses_parallel_tool_calls(llm_config: LLMConfig | None) -> bool:
838+
"""Resolve the default parallel_tool_calls setting for Responses API requests."""
839+
840+
if llm_config is not None:
841+
configured_value = llm_config.extra_params.get("parallel_tool_calls")
842+
if isinstance(configured_value, bool):
843+
return configured_value
844+
return True
845+
846+
837847
def call_llm_with_openai_responses(
838848
client: Any,
839849
kwargs: dict[str, Any],
@@ -872,6 +882,7 @@ def call_llm_with_openai_responses(
872882
stream_requested = bool(request_payload.pop("stream", False) or getattr(llm_config, "stream", False))
873883

874884
request_payload.pop("store", None)
885+
request_payload.setdefault("parallel_tool_calls", _default_openai_responses_parallel_tool_calls(llm_config))
875886

876887
# Always request encrypted reasoning content so that reasoning items can be
877888
# passed back in subsequent conversation turns (required for stateless / ZDR mode).

tests/unit/test_llm_caller.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import pytest
3434

35+
from nexau.archs.llm.llm_config import LLMConfig
3536
from nexau.archs.main_sub.execution.hooks import MiddlewareManager
3637
from nexau.archs.main_sub.execution.llm_caller import LLMCaller
3738
from nexau.archs.main_sub.execution.model_response import ModelResponse
@@ -187,6 +188,45 @@ def test_call_llm_success_responses_api(self, mock_openai_client, responses_llm_
187188
]
188189
assert call_kwargs["input"] == expected_input
189190
assert call_kwargs["max_output_tokens"] == 120
191+
assert call_kwargs["parallel_tool_calls"] is True
192+
193+
def test_call_llm_success_responses_api_honors_parallel_tool_calls_override(self, mock_openai_client, agent_state):
194+
"""Responses API should preserve explicit parallel_tool_calls=False from LLMConfig extra kwargs."""
195+
responses_payload = SimpleNamespace(
196+
output=[
197+
{
198+
"type": "message",
199+
"role": "assistant",
200+
"status": "completed",
201+
"content": [{"type": "output_text", "text": "Hello from responses!"}],
202+
}
203+
],
204+
output_text="Hello from responses!",
205+
model="gpt-4o-mini",
206+
usage=SimpleNamespace(input_tokens=10, output_tokens=5),
207+
)
208+
mock_openai_client.responses.create.return_value = responses_payload
209+
210+
llm_config = LLMConfig(
211+
model="gpt-4o-mini",
212+
base_url="https://api.openai.com/v1",
213+
api_key="test-key",
214+
temperature=0.1,
215+
max_tokens=1000,
216+
api_type="openai_responses",
217+
parallel_tool_calls=False,
218+
)
219+
caller = LLMCaller(
220+
openai_client=mock_openai_client,
221+
llm_config=llm_config,
222+
)
223+
224+
messages = [Message.user("Hello")]
225+
response = caller.call_llm(messages, max_tokens=120, force_stop_reason=AgentStopReason.SUCCESS, agent_state=agent_state)
226+
227+
assert isinstance(response, ModelResponse)
228+
call_kwargs = mock_openai_client.responses.create.call_args.kwargs
229+
assert call_kwargs["parallel_tool_calls"] is False
190230

191231
def test_call_llm_responses_api_carries_reasoning(self, mock_openai_client, responses_llm_config, agent_state):
192232
"""Reasoning items should be preserved for subsequent turns."""

0 commit comments

Comments
 (0)