From 65d495bb7fabe46c54647ce4a38e56170517e5b6 Mon Sep 17 00:00:00 2001
From: alex s <46074070+bearsyankees@users.noreply.github.com>
Date: Wed, 16 Sep 2026 11:13:30 -0400
Subject: [PATCH] feat(config): STRIX_API_TYPE forces responses vs chat
completions (#1324)
* Add api_type field to LlmSettings
Added 'api_type' field to LlmSettings for API path selection.
* Refactor API type handling in models.py
* Implement test for LlmSettings API type
Add test for API type override settings in LlmSettings.
* fix(tests): lint api_type test, cover the api_base override route, document STRIX_API_TYPE
* fix(models): keep LiteLLM chat-completions tool schema when STRIX_API_TYPE=responses
---------
Co-authored-by: RAJVARDHAN <95933896+vardhans07@users.noreply.github.com>
---
docs/advanced/configuration.mdx | 6 ++++
strix/config/models.py | 10 +++++--
strix/config/settings.py | 6 ++++
tests/test_models.py | 50 +++++++++++++++++++++++++++++++++
4 files changed, 69 insertions(+), 3 deletions(-)
diff --git a/docs/advanced/configuration.mdx b/docs/advanced/configuration.mdx
index 98a9d690..d2108e68 100644
--- a/docs/advanced/configuration.mdx
+++ b/docs/advanced/configuration.mdx
@@ -19,6 +19,12 @@ Configure Strix using environment variables or a config file.
Custom API base URL. Also accepts `OPENAI_API_BASE`, `LITELLM_BASE_URL`, or `OLLAMA_API_BASE`.
+
+ Select the OpenAI API path for the model: `responses` or `chat_completions`.
+ By default, a custom `LLM_API_BASE` uses chat completions. Set this variable
+ when your gateway requires the other API. Also accepts `STRIX_FORCE_API`.
+
+
Extra HTTP headers sent on every LLM request, as a JSON object (e.g.
`{"X-Feature-Key":"value","X-Tenant":"acme"}`). Useful for OpenAI-compatible
diff --git a/strix/config/models.py b/strix/config/models.py
index 3babbe37..d8444ee1 100644
--- a/strix/config/models.py
+++ b/strix/config/models.py
@@ -632,9 +632,11 @@ def configure_sdk_model_defaults(settings: Settings) -> None:
if llm.api_base:
os.environ["OPENAI_BASE_URL"] = llm.api_base
_configure_litellm_default("api_base", llm.api_base)
- set_default_openai_api("chat_completions")
- else:
- set_default_openai_api("responses")
+ api_type = llm.api_type
+ if api_type is None:
+ api_type = "chat_completions" if llm.api_base else "responses"
+
+ set_default_openai_api(api_type)
_configure_extra_headers(llm)
@@ -809,6 +811,8 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo
model = model_name.strip().lower()
if "/" in model and not model.startswith("openai/"):
return True
+ if settings.llm.api_type is not None:
+ return settings.llm.api_type == "chat_completions"
if settings.llm.api_base:
return True
return not model_supports_reasoning(model_name)
diff --git a/strix/config/settings.py b/strix/config/settings.py
index 9309ac39..7bf1de7f 100644
--- a/strix/config/settings.py
+++ b/strix/config/settings.py
@@ -9,6 +9,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]
+ApiType = Literal["responses", "chat_completions"]
DEFAULT_MAX_TURNS = 500
@@ -23,6 +24,11 @@ class LlmSettings(BaseSettings):
model_config = _BASE_CONFIG
model: str | None = Field(default=None, alias="STRIX_LLM")
+ api_type: ApiType | None = Field(
+ default=None,
+ validation_alias=AliasChoices("STRIX_API_TYPE", "STRIX_FORCE_API"),
+ description="Force 'responses' or 'chat_completions' API path",
+ )
api_key: str | None = Field(
default=None,
validation_alias=AliasChoices("LLM_API_KEY", "OPENAI_API_KEY"),
diff --git a/tests/test_models.py b/tests/test_models.py
index cd11819a..7078ca98 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -2,20 +2,27 @@
from __future__ import annotations
+import litellm
import pytest
from agents.extensions.models.litellm_model import LitellmModel
from agents.model_settings import ModelSettings
+from agents.models import _openai_shared
+from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
+from agents.models.openai_responses import OpenAIResponsesModel
from strix.config.models import (
RECOMMENDED_MODEL_NAMES,
StrixProvider,
_NonStreamingModel,
_TurnGuardModel,
+ configure_sdk_model_defaults,
is_recommended_or_frontier_model,
request_timeout_extra_args,
routes_through_litellm,
supports_strict_tool_schemas,
+ uses_chat_completions_tool_schema,
)
+from strix.config.settings import Settings
@pytest.mark.parametrize("model_name", RECOMMENDED_MODEL_NAMES)
@@ -168,3 +175,46 @@ def test_routes_through_litellm_matches_the_provider(
while isinstance(model, _NonStreamingModel | _TurnGuardModel):
model = model._inner
assert isinstance(model, LitellmModel) is litellm
+
+
+def test_api_type_override_settings(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("STRIX_LLM", "gpt-4")
+ monkeypatch.setenv("STRIX_API_TYPE", "chat_completions")
+ assert uses_chat_completions_tool_schema("gpt-4", Settings()) is True
+ monkeypatch.setenv("STRIX_LLM", "openai/gpt-4")
+ monkeypatch.setenv("STRIX_API_TYPE", "responses")
+ assert uses_chat_completions_tool_schema("openai/gpt-4", Settings()) is False
+ monkeypatch.setenv("STRIX_LLM", "anthropic/claude-sonnet-4-5")
+ assert uses_chat_completions_tool_schema("anthropic/claude-sonnet-4-5", Settings()) is True
+
+
+@pytest.mark.parametrize(
+ ("api_type", "expected"),
+ [
+ (None, OpenAIChatCompletionsModel),
+ ("chat_completions", OpenAIChatCompletionsModel),
+ ("responses", OpenAIResponsesModel),
+ ],
+)
+def test_api_type_overrides_the_api_base_route(
+ monkeypatch: pytest.MonkeyPatch, api_type: str | None, expected: type
+) -> None:
+ """``LLM_API_BASE`` defaults to chat completions. ``STRIX_API_TYPE`` must win."""
+ monkeypatch.setattr(_openai_shared, "_use_responses_by_default", True)
+ monkeypatch.setattr(_openai_shared, "_default_openai_client", None)
+ monkeypatch.setattr(_openai_shared, "_default_openai_key", None)
+ monkeypatch.setattr(litellm, "api_key", None)
+ monkeypatch.setattr(litellm, "api_base", None)
+ monkeypatch.setenv("OPENAI_API_KEY", "test-key")
+ monkeypatch.setenv("OPENAI_BASE_URL", "")
+ monkeypatch.setenv("STRIX_LLM", "gpt-5")
+ monkeypatch.setenv("LLM_API_KEY", "test-key")
+ monkeypatch.setenv("LLM_API_BASE", "https://gateway.example/v1")
+ monkeypatch.delenv("STRIX_API_TYPE", raising=False)
+ if api_type is not None:
+ monkeypatch.setenv("STRIX_API_TYPE", api_type)
+ configure_sdk_model_defaults(Settings())
+ model = StrixProvider().get_model("gpt-5")
+ while isinstance(model, _NonStreamingModel | _TurnGuardModel):
+ model = model._inner
+ assert isinstance(model, expected)