Compare commits

...

5 Commits

Author SHA1 Message Date
Alex Schapiro
80c7f6cd60 fix(models): keep LiteLLM chat-completions tool schema when STRIX_API_TYPE=responses 2026-09-16 14:57:17 +00:00
Alex Schapiro
7968ab44c6 fix(tests): lint api_type test, cover the api_base override route, document STRIX_API_TYPE 2026-09-16 14:50:44 +00:00
RAJVARDHAN
bb15b95cf8 Implement test for LlmSettings API type
Add test for API type override settings in LlmSettings.
2026-09-15 19:24:16 +05:30
RAJVARDHAN
9585cea6d0 Refactor API type handling in models.py 2026-09-15 19:12:18 +05:30
RAJVARDHAN
2eac866b51 Add api_type field to LlmSettings
Added 'api_type' field to LlmSettings for API path selection.
2026-09-15 18:13:55 +05:30
4 changed files with 69 additions and 3 deletions

View File

@@ -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`.
</ParamField>
<ParamField path="STRIX_API_TYPE" type="string">
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`.
</ParamField>
<ParamField path="LLM_EXTRA_HEADERS" type="string">
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

View File

@@ -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)

View File

@@ -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"),

View File

@@ -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)