diff --git a/strix/core/inputs.py b/strix/core/inputs.py index 1326d8ac..7026cfe1 100644 --- a/strix/core/inputs.py +++ b/strix/core/inputs.py @@ -201,9 +201,12 @@ def make_model_settings( request_timeout: float | None = None, prompt_cache: bool = True, extra_headers: dict[str, str] | None = None, + has_tools: bool = True, ) -> ModelSettings: + # Azure OpenAI rejects any request that carries ``parallel_tool_calls`` + # without ``tools``, so tool-less requests must omit it entirely. model_settings = ModelSettings( - parallel_tool_calls=False, + parallel_tool_calls=False if has_tools else None, retry=DEFAULT_MODEL_RETRY, include_usage=True, extra_args=request_timeout_extra_args(request_timeout), diff --git a/strix/interface/main.py b/strix/interface/main.py index ceb14c26..06966f4c 100644 --- a/strix/interface/main.py +++ b/strix/interface/main.py @@ -224,6 +224,7 @@ async def warm_up_llm(show_model_warning: bool = True) -> None: request_timeout=llm.timeout, prompt_cache=False, extra_headers=settings.dedupe.extra_headers, + has_tools=False, ) if deduper_extra: merged = {**(deduper_settings.extra_args or {}), **deduper_extra} diff --git a/strix/interface/scan_setup.py b/strix/interface/scan_setup.py index 327769a3..1e795a5c 100644 --- a/strix/interface/scan_setup.py +++ b/strix/interface/scan_setup.py @@ -78,6 +78,7 @@ async def preflight_model_connection( request_timeout=resolved_settings.llm.timeout, prompt_cache=False, extra_headers=resolved_settings.llm.extra_headers, + has_tools=False, ) await asyncio.wait_for( model.get_response( diff --git a/strix/llm/compaction.py b/strix/llm/compaction.py index 09d36454..e40caf6a 100644 --- a/strix/llm/compaction.py +++ b/strix/llm/compaction.py @@ -294,6 +294,7 @@ async def _summarize(model: str, prompt: str, max_tokens: int) -> str | None: request_timeout=llm.timeout, prompt_cache=False, extra_headers=llm.extra_headers, + has_tools=False, ).resolve(ModelSettings(max_tokens=max_tokens)) try: response = ( diff --git a/strix/report/dedupe.py b/strix/report/dedupe.py index f848a6d6..1cc0a66a 100644 --- a/strix/report/dedupe.py +++ b/strix/report/dedupe.py @@ -62,6 +62,7 @@ def _dedupe_model_settings( # must never receive the main endpoint's credentials. A dedicated model # gets its own DEDUPE_LLM_EXTRA_HEADERS instead. extra_headers=dedupe.extra_headers if dedupe.model else llm.extra_headers, + has_tools=False, ) extra = _dedupe_extra_args(dedupe) if extra: diff --git a/tests/test_inputs.py b/tests/test_inputs.py index ed233262..7dea64be 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -299,6 +299,16 @@ def test_make_model_settings_forces_required_for_anyllm_routed_openai_model() -> assert settings.tool_choice == "required" +def test_make_model_settings_disables_parallel_tool_calls_by_default() -> None: + assert make_model_settings("none", model_name="gpt-4o").parallel_tool_calls is False + + +def test_make_model_settings_omits_parallel_tool_calls_without_tools() -> None: + settings = make_model_settings("none", model_name="gpt-4o", has_tools=False) + + assert settings.parallel_tool_calls is None + + def test_make_model_settings_sets_request_timeout() -> None: settings = make_model_settings( "none",