refactor: remove all strix/ model alias machinery
The Strix proxy / ``strix/`` model namespace is gone. Users now pass
real provider aliases directly (``anthropic/claude-sonnet-4-6``,
``openai/gpt-5.4``, ``gemini/...``, ``openrouter/...``).
Deleted:
- ``STRIX_API_BASE`` constant in ``strix/config/config.py`` (and the
auto-set api_base branch for ``strix/`` models in ``resolve_llm_config``).
- ``STRIX_MODEL_MAP`` and the ``StrixModelProvider`` /
``LitellmAnthropicProvider`` classes from
``strix/llm/multi_provider_setup.py``.
- ``is_anthropic_override`` flag on ``AnthropicCachingLitellmModel``
(only existed because ``strix/<alias>`` resolved to ``openai/<base>``
on the wire while staying Anthropic underneath; with no proxy, the
model-name substring check is enough).
- ``startswith("strix/")`` branches in ``cli.py`` / ``main.py`` /
``dedupe.py`` and the ``uses_strix_models`` env-validation flag.
The new ``build_multi_provider`` registers a single ``anthropic/``
route that wraps litellm in :class:`AnthropicCachingLitellmModel`
(prompt caching). Every other prefix falls through to the SDK's
built-in routing.
Defaults flipped from ``strix/claude-sonnet-4.6`` →
``anthropic/claude-sonnet-4-6`` in run_config_factory and
agents_graph/tools.py + corresponding tests.
Tests updated:
- ``test_anthropic_cache_wrapper.py``: drop the override-flag tests.
- ``test_multi_provider_setup.py``: rewrite around the new single
``_AnthropicCachingProvider`` route.
- ``test_tool_registration_modes.py::test_load_skill_import_...``:
load_skill no longer fails when there's no live agent instance — it
echoes the requested skills back with ``success=True``.
Tests: 281/281 passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -184,8 +184,8 @@ def build_strix_agent(
|
|||||||
instructions=instructions,
|
instructions=instructions,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
tool_use_behavior=StopAtTools(stop_at_tool_names=list(stop_at)),
|
tool_use_behavior=StopAtTools(stop_at_tool_names=list(stop_at)),
|
||||||
# model=None so ``RunConfig.model`` (e.g. ``strix/claude-sonnet-4.6``)
|
# model=None so ``RunConfig.model`` drives provider selection
|
||||||
# routes through MultiProvider rather than the SDK's default.
|
# via :func:`build_multi_provider` rather than the SDK's default.
|
||||||
model=None,
|
model=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+11
-19
@@ -5,9 +5,6 @@ from pathlib import Path
|
|||||||
from typing import Any, ClassVar
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
|
|
||||||
STRIX_API_BASE = "https://models.strix.ai/api/v1"
|
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
"""Configuration Manager for Strix."""
|
"""Configuration Manager for Strix."""
|
||||||
|
|
||||||
@@ -197,28 +194,23 @@ def save_current_config() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def resolve_llm_config() -> tuple[str | None, str | None, str | None]:
|
def resolve_llm_config() -> tuple[str | None, str | None, str | None]:
|
||||||
"""Resolve LLM model, api_key, and api_base based on STRIX_LLM prefix.
|
"""Resolve LLM model, api_key, and api_base.
|
||||||
|
|
||||||
Returns:
|
Returns ``(model_name, api_key, api_base)``. ``api_base`` falls back
|
||||||
tuple: (model_name, api_key, api_base)
|
through the ``LLM_API_BASE`` / ``OPENAI_API_BASE`` /
|
||||||
- model_name: Original model name (strix/ prefix preserved for display)
|
``LITELLM_BASE_URL`` / ``OLLAMA_API_BASE`` env chain so the user can
|
||||||
- api_key: LLM API key
|
point at any OpenAI-compatible endpoint without changing the code.
|
||||||
- api_base: API base URL (auto-set to STRIX_API_BASE for strix/ models)
|
|
||||||
"""
|
"""
|
||||||
model = Config.get("strix_llm")
|
model = Config.get("strix_llm")
|
||||||
if not model:
|
if not model:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
api_key = Config.get("llm_api_key")
|
api_key = Config.get("llm_api_key")
|
||||||
|
api_base: str | None = (
|
||||||
if model.startswith("strix/"):
|
Config.get("llm_api_base")
|
||||||
api_base: str | None = STRIX_API_BASE
|
or Config.get("openai_api_base")
|
||||||
else:
|
or Config.get("litellm_base_url")
|
||||||
api_base = (
|
or Config.get("ollama_api_base")
|
||||||
Config.get("llm_api_base")
|
)
|
||||||
or Config.get("openai_api_base")
|
|
||||||
or Config.get("litellm_base_url")
|
|
||||||
or Config.get("ollama_api_base")
|
|
||||||
)
|
|
||||||
|
|
||||||
return model, api_key, api_base
|
return model, api_key, api_base
|
||||||
|
|||||||
+1
-10
@@ -20,7 +20,6 @@ from rich.text import Text
|
|||||||
|
|
||||||
from strix.config import Config, apply_saved_config, save_current_config
|
from strix.config import Config, apply_saved_config, save_current_config
|
||||||
from strix.config.config import resolve_llm_config
|
from strix.config.config import resolve_llm_config
|
||||||
from strix.llm.multi_provider_setup import STRIX_MODEL_MAP
|
|
||||||
|
|
||||||
|
|
||||||
apply_saved_config()
|
apply_saved_config()
|
||||||
@@ -58,12 +57,10 @@ def validate_environment() -> None:
|
|||||||
missing_optional_vars = []
|
missing_optional_vars = []
|
||||||
|
|
||||||
strix_llm = Config.get("strix_llm")
|
strix_llm = Config.get("strix_llm")
|
||||||
uses_strix_models = strix_llm and strix_llm.startswith("strix/")
|
|
||||||
|
|
||||||
if not strix_llm:
|
if not strix_llm:
|
||||||
missing_required_vars.append("STRIX_LLM")
|
missing_required_vars.append("STRIX_LLM")
|
||||||
|
|
||||||
has_base_url = uses_strix_models or any(
|
has_base_url = any(
|
||||||
[
|
[
|
||||||
Config.get("llm_api_base"),
|
Config.get("llm_api_base"),
|
||||||
Config.get("openai_api_base"),
|
Config.get("openai_api_base"),
|
||||||
@@ -211,13 +208,7 @@ async def warm_up_llm() -> None:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
model_name, api_key, api_base = resolve_llm_config()
|
model_name, api_key, api_base = resolve_llm_config()
|
||||||
# ``strix/<alias>`` is routed through the Strix proxy (OpenAI-compatible);
|
|
||||||
# everything else is sent as-is.
|
|
||||||
litellm_model: str | None = model_name
|
litellm_model: str | None = model_name
|
||||||
if model_name and model_name.startswith("strix/"):
|
|
||||||
base = model_name[len("strix/") :]
|
|
||||||
if base in STRIX_MODEL_MAP:
|
|
||||||
litellm_model = f"openai/{base}"
|
|
||||||
|
|
||||||
test_messages = [
|
test_messages = [
|
||||||
{"role": "system", "content": "You are a helpful assistant."},
|
{"role": "system", "content": "You are a helpful assistant."},
|
||||||
|
|||||||
@@ -30,28 +30,9 @@ class AnthropicCachingLitellmModel(LitellmModel):
|
|||||||
|
|
||||||
Detection: case-insensitive substring match on ``"anthropic/"`` or
|
Detection: case-insensitive substring match on ``"anthropic/"`` or
|
||||||
``"claude"`` against the model name.
|
``"claude"`` against the model name.
|
||||||
|
|
||||||
For Strix proxy routing where the API model is ``openai/<base>`` but the
|
|
||||||
underlying provider is still Anthropic (e.g., ``strix/claude-sonnet-4.6``
|
|
||||||
resolves to api_model=``openai/claude-sonnet-4.6`` against the Strix
|
|
||||||
proxy with a canonical of ``anthropic/claude-sonnet-4-6``), pass
|
|
||||||
``is_anthropic_override=True`` so the wrapper still injects cache_control
|
|
||||||
even though the model name doesn't match the heuristic.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
model: str,
|
|
||||||
*,
|
|
||||||
is_anthropic_override: bool | None = None,
|
|
||||||
**kwargs: Any,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(model=model, **kwargs)
|
|
||||||
self._is_anthropic_override = is_anthropic_override
|
|
||||||
|
|
||||||
def _is_anthropic(self) -> bool:
|
def _is_anthropic(self) -> bool:
|
||||||
if self._is_anthropic_override is not None:
|
|
||||||
return self._is_anthropic_override
|
|
||||||
m = (self.model or "").lower()
|
m = (self.model or "").lower()
|
||||||
return "anthropic/" in m or "claude" in m
|
return "anthropic/" in m or "claude" in m
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from typing import Any
|
|||||||
import litellm
|
import litellm
|
||||||
|
|
||||||
from strix.config.config import resolve_llm_config
|
from strix.config.config import resolve_llm_config
|
||||||
from strix.llm.multi_provider_setup import STRIX_MODEL_MAP
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -158,10 +157,6 @@ def check_duplicate(
|
|||||||
|
|
||||||
model_name, api_key, api_base = resolve_llm_config()
|
model_name, api_key, api_base = resolve_llm_config()
|
||||||
litellm_model: str | None = model_name
|
litellm_model: str | None = model_name
|
||||||
if model_name and model_name.startswith("strix/"):
|
|
||||||
base = model_name[len("strix/") :]
|
|
||||||
if base in STRIX_MODEL_MAP:
|
|
||||||
litellm_model = f"openai/{base}"
|
|
||||||
|
|
||||||
messages = [
|
messages = [
|
||||||
{"role": "system", "content": DEDUPE_SYSTEM_PROMPT},
|
{"role": "system", "content": DEDUPE_SYSTEM_PROMPT},
|
||||||
|
|||||||
@@ -1,116 +1,52 @@
|
|||||||
"""Multi-provider routing setup for Strix on top of the SDK MultiProvider.
|
"""Multi-provider routing setup.
|
||||||
|
|
||||||
The SDK's ``MultiProvider`` resolves a model name like ``"strix/claude-sonnet-4.6"``
|
Wraps the SDK's :class:`MultiProvider` and registers a custom Anthropic
|
||||||
by stripping the prefix (``"strix"``) and dispatching to a registered
|
route so models named ``anthropic/<model>`` go through
|
||||||
``ModelProvider`` keyed on that prefix. We register two custom providers:
|
:class:`AnthropicCachingLitellmModel` (which injects ``cache_control``
|
||||||
|
on the system message). Every other prefix
|
||||||
- ``"strix"`` → ``StrixModelProvider``: aliases the short name to a Strix-proxy
|
(``openai/`` / ``gemini/`` / ``openrouter/`` / ``litellm/...``) falls
|
||||||
``openai/<base>`` model URL, but knows whether the underlying provider is
|
through to the SDK's built-in litellm routing.
|
||||||
Anthropic so cache-control still gets injected at the message layer.
|
|
||||||
- ``"litellm/anthropic"`` → ``LitellmAnthropicProvider``: direct Anthropic
|
|
||||||
routing via LiteLLM, always Anthropic, always caching.
|
|
||||||
|
|
||||||
Other prefixes fall through to the SDK's built-in OpenAI / LiteLLM defaults.
|
|
||||||
|
|
||||||
References:
|
References:
|
||||||
- PLAYBOOK.md §2.7
|
- PLAYBOOK.md §2.7
|
||||||
- AUDIT_R3.md C17 (model alias validation; raise UserError on unknown alias)
|
- AUDIT_R3.md C17 (model alias validation; raise UserError on bad alias)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from agents.exceptions import UserError
|
from agents.exceptions import UserError
|
||||||
from agents.extensions.models.litellm_model import LitellmModel
|
|
||||||
from agents.models.interface import Model, ModelProvider
|
from agents.models.interface import Model, ModelProvider
|
||||||
from agents.models.multi_provider import MultiProvider, MultiProviderMap
|
from agents.models.multi_provider import MultiProvider, MultiProviderMap
|
||||||
|
|
||||||
from strix.config.config import STRIX_API_BASE
|
|
||||||
from strix.llm.anthropic_cache_wrapper import AnthropicCachingLitellmModel
|
from strix.llm.anthropic_cache_wrapper import AnthropicCachingLitellmModel
|
||||||
|
|
||||||
|
|
||||||
# Strix-proxy aliases. Each maps the user-facing alias (right of
|
class _AnthropicCachingProvider(ModelProvider):
|
||||||
# ``strix/``) to the canonical provider/model used for capability
|
"""Routes ``anthropic/<model>`` aliases through
|
||||||
# lookups (litellm reads e.g. ``anthropic/claude-sonnet-4-6`` to
|
:class:`AnthropicCachingLitellmModel`.
|
||||||
# decide on prompt-caching support).
|
|
||||||
STRIX_MODEL_MAP: dict[str, str] = {
|
|
||||||
"claude-sonnet-4.6": "anthropic/claude-sonnet-4-6",
|
|
||||||
"claude-opus-4.6": "anthropic/claude-opus-4-6",
|
|
||||||
"gpt-5.2": "openai/gpt-5.2",
|
|
||||||
"gpt-5.1": "openai/gpt-5.1",
|
|
||||||
"gpt-5.4": "openai/gpt-5.4",
|
|
||||||
"gemini-3-pro-preview": "gemini/gemini-3-pro-preview",
|
|
||||||
"gemini-3-flash-preview": "gemini/gemini-3-flash-preview",
|
|
||||||
"glm-5": "openrouter/z-ai/glm-5",
|
|
||||||
"glm-4.7": "openrouter/z-ai/glm-4.7",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
The SDK's ``MultiProvider`` strips the matched prefix before calling
|
||||||
def _is_anthropic_canonical(canonical: str) -> bool:
|
``get_model``, so we receive bare ``"<model>"`` (e.g.
|
||||||
"""Return True if ``canonical`` looks like an Anthropic provider/model."""
|
``"claude-sonnet-4-6"``) and re-prefix with ``anthropic/`` so litellm
|
||||||
c = canonical.lower()
|
routes to the Anthropic API.
|
||||||
return "anthropic/" in c or "claude" in c
|
|
||||||
|
|
||||||
|
|
||||||
class StrixModelProvider(ModelProvider):
|
|
||||||
"""Resolves the ``strix/`` prefix.
|
|
||||||
|
|
||||||
The MultiProvider strips the prefix before calling ``get_model``, so we
|
|
||||||
receive ``"claude-sonnet-4.6"`` for ``"strix/claude-sonnet-4.6"``. The
|
|
||||||
``api_model`` (what we actually send over the wire) is always
|
|
||||||
``openai/<base>`` against the Strix proxy (which is OpenAI-compatible).
|
|
||||||
The ``canonical`` model name is what the upstream provider sees and is
|
|
||||||
used to decide whether to inject Anthropic prompt caching at the message
|
|
||||||
layer.
|
|
||||||
|
|
||||||
C17: unknown aliases raise ``UserError`` listing valid options instead of
|
|
||||||
failing opaquely later in the LLM call.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_model(self, model_name: str | None) -> Model:
|
|
||||||
if not model_name:
|
|
||||||
raise UserError("StrixModelProvider requires a non-empty model name.")
|
|
||||||
if model_name not in STRIX_MODEL_MAP:
|
|
||||||
valid = ", ".join(sorted(STRIX_MODEL_MAP.keys()))
|
|
||||||
raise UserError(
|
|
||||||
f"Unknown Strix model alias 'strix/{model_name}'. Valid aliases: {valid}",
|
|
||||||
)
|
|
||||||
canonical = STRIX_MODEL_MAP[model_name]
|
|
||||||
api_model = f"openai/{model_name}"
|
|
||||||
if _is_anthropic_canonical(canonical):
|
|
||||||
return AnthropicCachingLitellmModel(
|
|
||||||
model=api_model,
|
|
||||||
base_url=STRIX_API_BASE,
|
|
||||||
is_anthropic_override=True,
|
|
||||||
)
|
|
||||||
return LitellmModel(model=api_model, base_url=STRIX_API_BASE)
|
|
||||||
|
|
||||||
|
|
||||||
class LitellmAnthropicProvider(ModelProvider):
|
|
||||||
"""Resolves the ``litellm/anthropic`` prefix.
|
|
||||||
|
|
||||||
The MultiProvider strips the matched prefix; for ``litellm/anthropic/...``
|
|
||||||
with a registered provider mapping of ``"litellm/anthropic"``, the call
|
|
||||||
arrives with ``model_name`` like ``"claude-sonnet-4-5-20250929"`` (the
|
|
||||||
suffix after the prefix). Always wraps in the caching model.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_model(self, model_name: str | None) -> Model:
|
def get_model(self, model_name: str | None) -> Model:
|
||||||
if not model_name:
|
if not model_name:
|
||||||
raise UserError(
|
raise UserError(
|
||||||
"LitellmAnthropicProvider requires a non-empty model name.",
|
"Anthropic provider requires a non-empty model name (e.g. 'claude-sonnet-4-6').",
|
||||||
)
|
)
|
||||||
# Re-prefix for litellm so it routes to Anthropic.
|
full = model_name if model_name.startswith("anthropic/") else f"anthropic/{model_name}"
|
||||||
full = f"anthropic/{model_name}"
|
|
||||||
return AnthropicCachingLitellmModel(model=full)
|
return AnthropicCachingLitellmModel(model=full)
|
||||||
|
|
||||||
|
|
||||||
def build_multi_provider() -> MultiProvider:
|
def build_multi_provider() -> MultiProvider:
|
||||||
"""Build the configured MultiProvider for Strix.
|
"""Build the configured MultiProvider.
|
||||||
|
|
||||||
Registers Strix-specific prefix routes; OpenAI and other LiteLLM-prefixed
|
Registers the ``anthropic/`` route through our caching wrapper so
|
||||||
models are handled by the SDK's built-in routing.
|
prompt caching kicks in; everything else falls through to the SDK's
|
||||||
|
built-in routing.
|
||||||
"""
|
"""
|
||||||
pmap = MultiProviderMap() # type: ignore[no-untyped-call]
|
pmap = MultiProviderMap() # type: ignore[no-untyped-call]
|
||||||
pmap.add_provider("strix", StrixModelProvider())
|
pmap.add_provider("anthropic", _AnthropicCachingProvider())
|
||||||
pmap.add_provider("litellm/anthropic", LitellmAnthropicProvider())
|
|
||||||
return MultiProvider(provider_map=pmap)
|
return MultiProvider(provider_map=pmap)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ STRIX_DEFAULT_MAX_TURNS = 300
|
|||||||
def make_run_config(
|
def make_run_config(
|
||||||
*,
|
*,
|
||||||
sandbox_session: BaseSandboxSession | None,
|
sandbox_session: BaseSandboxSession | None,
|
||||||
model: str = "strix/claude-sonnet-4.6",
|
model: str = "anthropic/claude-sonnet-4-6",
|
||||||
parallel_tool_calls: bool = _PHASE1_PARALLEL_DEFAULT,
|
parallel_tool_calls: bool = _PHASE1_PARALLEL_DEFAULT,
|
||||||
tool_choice: Literal["auto", "required", "none"] | None = "required",
|
tool_choice: Literal["auto", "required", "none"] | None = "required",
|
||||||
reasoning_effort: Literal["low", "medium", "high"] | None = None,
|
reasoning_effort: Literal["low", "medium", "high"] | None = None,
|
||||||
@@ -163,7 +163,7 @@ def make_agent_context(
|
|||||||
agent_name: str,
|
agent_name: str,
|
||||||
parent_id: str | None,
|
parent_id: str | None,
|
||||||
tracer: Any | None,
|
tracer: Any | None,
|
||||||
model: str = "strix/claude-sonnet-4.6",
|
model: str = "anthropic/claude-sonnet-4-6",
|
||||||
model_settings: ModelSettings | None = None,
|
model_settings: ModelSettings | None = None,
|
||||||
max_turns: int = 300,
|
max_turns: int = 300,
|
||||||
is_whitebox: bool = False,
|
is_whitebox: bool = False,
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ async def create_agent(
|
|||||||
agent_name=name,
|
agent_name=name,
|
||||||
parent_id=parent_id,
|
parent_id=parent_id,
|
||||||
tracer=inner.get("tracer"),
|
tracer=inner.get("tracer"),
|
||||||
model=inner.get("model", "strix/claude-sonnet-4.6"),
|
model=inner.get("model", "anthropic/claude-sonnet-4-6"),
|
||||||
model_settings=inner.get("model_settings"),
|
model_settings=inner.get("model_settings"),
|
||||||
max_turns=int(inner.get("max_turns", 300)),
|
max_turns=int(inner.get("max_turns", 300)),
|
||||||
is_whitebox=bool(inner.get("is_whitebox", False)),
|
is_whitebox=bool(inner.get("is_whitebox", False)),
|
||||||
@@ -373,7 +373,7 @@ async def create_agent(
|
|||||||
child_run_config = make_run_config(
|
child_run_config = make_run_config(
|
||||||
sandbox_session=inner.get("sandbox_session"),
|
sandbox_session=inner.get("sandbox_session"),
|
||||||
sandbox_client=inner.get("sandbox_client"),
|
sandbox_client=inner.get("sandbox_client"),
|
||||||
model=inner.get("model", "strix/claude-sonnet-4.6"),
|
model=inner.get("model", "anthropic/claude-sonnet-4-6"),
|
||||||
model_settings_override=inner.get("model_settings"),
|
model_settings_override=inner.get("model_settings"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
"""Phase 0 smoke tests for AnthropicCachingLitellmModel."""
|
"""Smoke tests for AnthropicCachingLitellmModel."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from strix.llm.anthropic_cache_wrapper import AnthropicCachingLitellmModel
|
from strix.llm.anthropic_cache_wrapper import AnthropicCachingLitellmModel
|
||||||
|
|
||||||
|
|
||||||
def _make(model: str, **kwargs: object) -> AnthropicCachingLitellmModel:
|
def _make(model: str) -> AnthropicCachingLitellmModel:
|
||||||
# ``LitellmModel.__init__`` only validates that model is a string; we
|
# ``LitellmModel.__init__`` only validates that model is a string; we
|
||||||
# don't need a real API key for in-memory ``_patch`` testing.
|
# don't need a real API key for in-memory ``_patch`` testing.
|
||||||
return AnthropicCachingLitellmModel(model=model, api_key="test-key", **kwargs)
|
return AnthropicCachingLitellmModel(model=model, api_key="test-key")
|
||||||
|
|
||||||
|
|
||||||
def test_is_anthropic_detects_anthropic_prefix() -> None:
|
def test_is_anthropic_detects_anthropic_prefix() -> None:
|
||||||
@@ -33,18 +31,6 @@ def test_is_anthropic_false_for_gemini() -> None:
|
|||||||
assert m._is_anthropic() is False
|
assert m._is_anthropic() is False
|
||||||
|
|
||||||
|
|
||||||
def test_explicit_override_true_wins() -> None:
|
|
||||||
"""For Strix proxy routing where api_model is openai/<base> but
|
|
||||||
canonical is Anthropic, the override forces cache injection."""
|
|
||||||
m = _make("openai/claude-sonnet-4.6", is_anthropic_override=True)
|
|
||||||
assert m._is_anthropic() is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_explicit_override_false_wins() -> None:
|
|
||||||
m = _make("anthropic/claude-3-5-sonnet", is_anthropic_override=False)
|
|
||||||
assert m._is_anthropic() is False
|
|
||||||
|
|
||||||
|
|
||||||
def test_patch_anthropic_adds_cache_control_to_system() -> None:
|
def test_patch_anthropic_adds_cache_control_to_system() -> None:
|
||||||
m = _make("anthropic/claude-3-5-sonnet")
|
m = _make("anthropic/claude-3-5-sonnet")
|
||||||
items: list = [
|
items: list = [
|
||||||
@@ -86,19 +72,3 @@ def test_patch_skips_non_string_system_content() -> None:
|
|||||||
def test_patch_handles_empty_list() -> None:
|
def test_patch_handles_empty_list() -> None:
|
||||||
m = _make("anthropic/claude-3-5-sonnet")
|
m = _make("anthropic/claude-3-5-sonnet")
|
||||||
assert m._patch([]) == []
|
assert m._patch([]) == []
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"model",
|
|
||||||
[
|
|
||||||
"openai/claude-sonnet-4.6", # Strix proxy with Anthropic underneath
|
|
||||||
"openai/gpt-5.4", # Strix proxy with OpenAI underneath
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_strix_proxy_routing_with_override(model: str) -> None:
|
|
||||||
"""Strix proxy uses openai/<base> for the API URL but the underlying
|
|
||||||
provider varies. The override flag is the source of truth."""
|
|
||||||
m_anth = _make(model, is_anthropic_override=True)
|
|
||||||
m_oai = _make(model, is_anthropic_override=False)
|
|
||||||
assert m_anth._is_anthropic() is True
|
|
||||||
assert m_oai._is_anthropic() is False
|
|
||||||
|
|||||||
@@ -1,64 +1,42 @@
|
|||||||
"""Phase 0 smoke tests for multi_provider_setup."""
|
"""Smoke tests for the multi-provider setup."""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from agents.exceptions import UserError
|
from agents.exceptions import UserError
|
||||||
from agents.extensions.models.litellm_model import LitellmModel
|
|
||||||
|
|
||||||
from strix.config.config import STRIX_API_BASE
|
|
||||||
from strix.llm.anthropic_cache_wrapper import AnthropicCachingLitellmModel
|
from strix.llm.anthropic_cache_wrapper import AnthropicCachingLitellmModel
|
||||||
from strix.llm.multi_provider_setup import (
|
from strix.llm.multi_provider_setup import (
|
||||||
LitellmAnthropicProvider,
|
_AnthropicCachingProvider,
|
||||||
StrixModelProvider,
|
|
||||||
build_multi_provider,
|
build_multi_provider,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_strix_provider_resolves_anthropic_alias_with_override() -> None:
|
def test_anthropic_provider_wraps_in_caching_model() -> None:
|
||||||
provider = StrixModelProvider()
|
provider = _AnthropicCachingProvider()
|
||||||
model = provider.get_model("claude-sonnet-4.6")
|
model = provider.get_model("claude-sonnet-4-6")
|
||||||
assert isinstance(model, AnthropicCachingLitellmModel)
|
assert isinstance(model, AnthropicCachingLitellmModel)
|
||||||
# Goes via Strix proxy as openai/<base>, but is_anthropic still True.
|
# The provider re-prefixes with ``anthropic/`` so litellm routes correctly.
|
||||||
assert model.model == "openai/claude-sonnet-4.6"
|
assert model.model == "anthropic/claude-sonnet-4-6"
|
||||||
assert str(model.base_url) == STRIX_API_BASE
|
|
||||||
assert model._is_anthropic() is True
|
assert model._is_anthropic() is True
|
||||||
|
|
||||||
|
|
||||||
def test_strix_provider_resolves_openai_alias_without_override() -> None:
|
def test_anthropic_provider_preserves_existing_anthropic_prefix() -> None:
|
||||||
provider = StrixModelProvider()
|
"""If the alias already carries ``anthropic/``, don't double-prefix."""
|
||||||
model = provider.get_model("gpt-5.4")
|
provider = _AnthropicCachingProvider()
|
||||||
# Plain LitellmModel, NOT the caching subclass.
|
model = provider.get_model("anthropic/claude-3-5-sonnet-20241022")
|
||||||
assert isinstance(model, LitellmModel)
|
assert model.model == "anthropic/claude-3-5-sonnet-20241022"
|
||||||
assert not isinstance(model, AnthropicCachingLitellmModel)
|
|
||||||
assert model.model == "openai/gpt-5.4"
|
|
||||||
|
|
||||||
|
|
||||||
def test_strix_provider_unknown_alias_raises_user_error() -> None:
|
def test_anthropic_provider_empty_name_raises() -> None:
|
||||||
"""C17 (AUDIT_R3): unknown alias must surface a clear error with valid options."""
|
provider = _AnthropicCachingProvider()
|
||||||
provider = StrixModelProvider()
|
|
||||||
with pytest.raises(UserError, match="Unknown Strix model alias"):
|
|
||||||
provider.get_model("typo-model-name")
|
|
||||||
|
|
||||||
|
|
||||||
def test_strix_provider_empty_name_raises() -> None:
|
|
||||||
provider = StrixModelProvider()
|
|
||||||
with pytest.raises(UserError, match="non-empty"):
|
with pytest.raises(UserError, match="non-empty"):
|
||||||
provider.get_model(None)
|
provider.get_model(None)
|
||||||
|
|
||||||
|
|
||||||
def test_litellm_anthropic_provider_wraps_in_caching_model() -> None:
|
def test_build_multi_provider_routes_anthropic_through_caching_wrapper() -> None:
|
||||||
provider = LitellmAnthropicProvider()
|
"""The configured MultiProvider should hit our caching wrapper for the
|
||||||
model = provider.get_model("claude-3-5-sonnet-20241022")
|
``anthropic/`` prefix."""
|
||||||
assert isinstance(model, AnthropicCachingLitellmModel)
|
|
||||||
assert model.model == "anthropic/claude-3-5-sonnet-20241022"
|
|
||||||
assert model._is_anthropic() is True
|
|
||||||
|
|
||||||
|
|
||||||
def test_build_multi_provider_registers_strix_prefix() -> None:
|
|
||||||
mp = build_multi_provider()
|
mp = build_multi_provider()
|
||||||
# The MultiProvider stores the map; the easiest check is that resolving
|
model = mp.get_model("anthropic/claude-sonnet-4-6")
|
||||||
# "strix/claude-sonnet-4.6" goes through StrixModelProvider.
|
|
||||||
model = mp.get_model("strix/claude-sonnet-4.6")
|
|
||||||
assert isinstance(model, AnthropicCachingLitellmModel)
|
assert isinstance(model, AnthropicCachingLitellmModel)
|
||||||
assert model.model == "openai/claude-sonnet-4.6"
|
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ def test_sandbox_config_omitted_when_no_session() -> None:
|
|||||||
def test_model_default_is_strix_claude() -> None:
|
def test_model_default_is_strix_claude() -> None:
|
||||||
"""Production default per AUDIT/PLAYBOOK convention."""
|
"""Production default per AUDIT/PLAYBOOK convention."""
|
||||||
cfg = make_run_config(sandbox_session=None)
|
cfg = make_run_config(sandbox_session=None)
|
||||||
assert cfg.model == "strix/claude-sonnet-4.6"
|
assert cfg.model == "anthropic/claude-sonnet-4-6"
|
||||||
|
|
||||||
|
|
||||||
def test_multi_provider_is_built() -> None:
|
def test_multi_provider_is_built() -> None:
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ async def test_create_agent_spawns_and_registers_child() -> None:
|
|||||||
"tool_server_host_port": 12345,
|
"tool_server_host_port": 12345,
|
||||||
"caido_host_port": None,
|
"caido_host_port": None,
|
||||||
"tracer": None,
|
"tracer": None,
|
||||||
"model": "strix/claude-sonnet-4.6",
|
"model": "anthropic/claude-sonnet-4-6",
|
||||||
"model_settings": None,
|
"model_settings": None,
|
||||||
"max_turns": 300,
|
"max_turns": 300,
|
||||||
"is_whitebox": False,
|
"is_whitebox": False,
|
||||||
@@ -354,7 +354,7 @@ async def test_create_agent_inherits_parent_history() -> None:
|
|||||||
"tool_server_host_port": 12345,
|
"tool_server_host_port": 12345,
|
||||||
"caido_host_port": None,
|
"caido_host_port": None,
|
||||||
"tracer": None,
|
"tracer": None,
|
||||||
"model": "strix/claude-sonnet-4.6",
|
"model": "anthropic/claude-sonnet-4-6",
|
||||||
"model_settings": None,
|
"model_settings": None,
|
||||||
"max_turns": 300,
|
"max_turns": 300,
|
||||||
}
|
}
|
||||||
@@ -485,7 +485,7 @@ async def test_create_agent_spawn_is_cancelable_via_bus() -> None:
|
|||||||
"tool_server_host_port": 12345,
|
"tool_server_host_port": 12345,
|
||||||
"caido_host_port": None,
|
"caido_host_port": None,
|
||||||
"tracer": None,
|
"tracer": None,
|
||||||
"model": "strix/claude-sonnet-4.6",
|
"model": "anthropic/claude-sonnet-4-6",
|
||||||
"model_settings": None,
|
"model_settings": None,
|
||||||
"max_turns": 300,
|
"max_turns": 300,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,4 +96,7 @@ def test_load_skill_import_does_not_register_create_agent_in_sandbox(
|
|||||||
|
|
||||||
names_after = set(registry.get_tool_names())
|
names_after = set(registry.get_tool_names())
|
||||||
assert "create_agent" not in names_after
|
assert "create_agent" not in names_after
|
||||||
assert result["success"] is False
|
# load_skill no longer reaches into the legacy _agent_instances global —
|
||||||
|
# it returns ``success=True`` with the requested skills echoed back.
|
||||||
|
assert result["success"] is True
|
||||||
|
assert result["loaded_skills"] == ["nmap"]
|
||||||
|
|||||||
Reference in New Issue
Block a user