strix/tests/test_models.py
Ahmed Allam 0ab7244807 feat(models): refresh the recommended model list and docs examples
Add Claude Fable 5.1, Gemini 3.7 Flash, and Z.ai GLM-5.3 / GLM-5.3-Flash
to RECOMMENDED_MODEL_NAMES, add a Z.ai GLM frontier family so GLM-5.x is
accepted through OpenRouter and Novita routes, and drop the superseded
GPT-5.4, GPT-5.3-codex, Opus 4.8, Sonnet 4.6, Gemini 3.6 Flash, and
Qwen3.7 entries. Update the README, docs provider pages, quickstart, and
CLI hint strings to the same current models, including DeepSeek V4,
Kimi K3, and GLM-5.3.
2026-09-02 16:52:53 +03:00

164 lines
5.4 KiB
Python

"""Tests for LLM model recommendation helpers."""
from __future__ import annotations
import pytest
from agents.extensions.models.litellm_model import LitellmModel
from agents.model_settings import ModelSettings
from strix.config.models import (
RECOMMENDED_MODEL_NAMES,
StrixProvider,
_NonStreamingModel,
_TurnGuardModel,
is_recommended_or_frontier_model,
request_timeout_extra_args,
routes_through_litellm,
supports_strict_tool_schemas,
)
@pytest.mark.parametrize("model_name", RECOMMENDED_MODEL_NAMES)
def test_recommended_models_are_accepted(model_name: str) -> None:
assert is_recommended_or_frontier_model(model_name)
def test_request_timeout_extra_args_positive() -> None:
assert request_timeout_extra_args(300) == {"timeout": 300}
assert request_timeout_extra_args(10) == {"timeout": 10}
def test_request_timeout_extra_args_survives_model_settings_json_dump() -> None:
"""The Chat Completions and LiteLLM paths pydantic-serialize ModelSettings for
their tracing span; a non-JSON-serializable timeout fails every turn there."""
settings = ModelSettings(extra_args=request_timeout_extra_args(300))
assert settings.to_json_dict()["extra_args"] == {"timeout": 300}
@pytest.mark.parametrize("value", [None, 0, -1])
def test_request_timeout_extra_args_disabled(value: float | None) -> None:
assert request_timeout_extra_args(value) is None
def test_recommended_models_are_matched_case_insensitively() -> None:
assert is_recommended_or_frontier_model("Vertex_AI/Gemini-3-Pro-Preview")
@pytest.mark.parametrize(
"model_name",
[
"gpt-5.5",
"chatgpt/gpt-5.4",
"litellm/openai/gpt-5.4-pro",
"azure_ai/gpt-5.5-pro",
"bedrock_mantle/openai.gpt-5.5",
"anthropic/claude-opus-5",
"anthropic/claude-opus-4-8",
"anthropic.claude-opus-4-8",
"anthropic/claude-opus-4-7",
"anthropic/claude-fable-5",
"anthropic/claude-sonnet-5",
"vertex_ai/claude-sonnet-5@default",
"vertex_ai/claude-sonnet-4-6@default",
"any-llm/anthropic/claude-sonnet-4-6",
"vertex_ai/gemini-3.1-pro-preview",
"openrouter/google/gemini-3.1-pro-preview",
"deepseek/deepseek-v4-pro",
"deepseek/deepseek-r1-0528",
"deepseek/deepseek-reasoner",
"dashscope/qwen3-max-2026-01-23",
"qwen3.7-max",
"dashscope/qwen3.8-max",
"moonshot/kimi-k2.6",
"kimi-k2.7-code",
"moonshot/kimi-k3",
"anthropic/claude-fable-5-1",
"vertex_ai/claude-fable-5-1@default",
"gemini/gemini-3.7-flash",
"glm-5.3",
"zai/glm-5.3-flash",
"openrouter/z-ai/glm-5.3",
"novita/zai-org/glm-5.2",
],
)
def test_frontier_model_families_are_accepted(model_name: str) -> None:
assert is_recommended_or_frontier_model(model_name)
@pytest.mark.parametrize(
"model_name",
[
"",
"openai/gpt-4.1",
"anthropic/claude-3-5-sonnet-latest",
"ollama/llama3.1",
"deepseek/deepseek-chat",
"custom-ollama/gpt-5-mini-local",
"custom-provider/claude-opus-4-local",
"xai/grok-4.5",
"openrouter/x-ai/grok-4",
"mistral/mistral-medium-3-5",
"mistral/magistral-medium-latest",
"zai/glm-4.7",
"openrouter/z-ai/glm-5",
"custom-provider/glm-5.3-local",
],
)
def test_non_frontier_models_are_rejected(model_name: str) -> None:
assert not is_recommended_or_frontier_model(model_name)
@pytest.mark.parametrize(
"model_name",
[
"anthropic/claude-sonnet-4-6",
"bedrock/anthropic.claude-opus-4-8-v1:0",
"vertex_ai/claude-sonnet-5",
"Sonnet-5",
],
)
def test_claude_routes_reject_strict_tool_schemas(model_name: str) -> None:
assert not supports_strict_tool_schemas(model_name)
@pytest.mark.parametrize(
"model_name",
["openai/gpt-5.4", "gpt-5.4", "gemini/gemini-3.1-pro-preview", "deepseek/deepseek-v4"],
)
def test_other_routes_keep_strict_tool_schemas(model_name: str) -> None:
assert supports_strict_tool_schemas(model_name)
@pytest.mark.parametrize(
("model_name", "litellm"),
[
("claude-sonnet-4-5", False),
("openai/claude-sonnet-4-5", False),
("any-llm/anthropic/claude-sonnet-4-5", False),
("anthropic/claude-sonnet-4-5", True),
("litellm/anthropic/claude-sonnet-4-5", True),
("bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0", True),
("ollama/llama3", True),
],
)
def test_routes_through_litellm_matches_the_provider(
monkeypatch: pytest.MonkeyPatch, model_name: str, litellm: bool
) -> None:
"""The helper must agree with what StrixProvider actually builds.
Callers use it to decide whether a LiteLLM-only request field is safe to
attach; on the SDK's own clients such a field raises TypeError mid-turn, so
drift here breaks every request on that route.
"""
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
assert routes_through_litellm(model_name) is litellm
try:
model = StrixProvider().get_model(model_name)
except ImportError:
# any-llm's client is an optional dependency; reaching it at all already
# proves the route is not LiteLLM's.
assert not litellm
return
while isinstance(model, _NonStreamingModel | _TurnGuardModel):
model = model._inner
assert isinstance(model, LitellmModel) is litellm