Stop exposing litellm/ prefix in user-facing model names

Users had to type STRIX_LLM=litellm/deepseek/deepseek-chat — the
litellm/ wrapper was Strix-internal plumbing surfacing in user config.

Add StrixProvider, a MultiProvider subclass that routes any non-OpenAI
prefix (deepseek/, anthropic/, groq/, xai/, mistral/, openrouter/, …)
through LitellmProvider with the prefix preserved. normalize_model_name
no longer adds litellm/ to anything; bare claude-* / gemini-* shorthands
expand to anthropic/<model> / gemini/<model> instead of the wrapped form.

Wire StrixProvider into warm_up_llm and RunConfig.model_provider.
litellm/<provider>/<model> and any-llm/<provider>/<model> still resolve
unchanged for users on older config.

Refresh stale model names in the env-validation messages and the
warm-up hint (gpt-5.4, claude-opus-4-7, deepseek-reasoner).

Verified 24-case end-to-end matrix: OpenAI direct vs. LitellmProvider
routing, env-var mirroring via validate_environment, supports_reasoning
detection, and tool_choice gating all behave correctly across modern
providers including the user's unknown DeepSeek SKU.
This commit is contained in:
0xallam
2026-06-08 01:03:29 +03:00
committed by Ahmed Allam
parent 712c64f630
commit 232711be8c
3 changed files with 36 additions and 15 deletions
+27 -8
View File
@@ -6,6 +6,7 @@ import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from agents import set_default_openai_api, set_default_openai_key, set_tracing_disabled from agents import set_default_openai_api, set_default_openai_key, set_tracing_disabled
from agents.models.multi_provider import MultiProvider
from agents.retry import ( from agents.retry import (
ModelRetryBackoffSettings, ModelRetryBackoffSettings,
ModelRetrySettings, ModelRetrySettings,
@@ -14,10 +15,31 @@ from agents.retry import (
if TYPE_CHECKING: if TYPE_CHECKING:
from agents.models.interface import ModelProvider
from strix.config.settings import Settings from strix.config.settings import Settings
_SDK_PREFIXES = {"any-llm", "litellm", "openai"} class StrixProvider(MultiProvider):
"""Route any non-OpenAI prefix through LiteLLM with the prefix preserved,
so users type ``deepseek/deepseek-chat`` rather than
``litellm/deepseek/deepseek-chat``.
"""
def _resolve_prefixed_model(
self,
*,
original_model_name: str,
prefix: str,
stripped_model_name: str | None,
) -> tuple[ModelProvider, str | None]:
if prefix in {"openai", "litellm", "any-llm"}:
return super()._resolve_prefixed_model(
original_model_name=original_model_name,
prefix=prefix,
stripped_model_name=stripped_model_name,
)
return self._get_fallback_provider("litellm"), original_model_name
DEFAULT_MODEL_RETRY = ModelRetrySettings( DEFAULT_MODEL_RETRY = ModelRetrySettings(
@@ -99,16 +121,13 @@ def normalize_model_name(model_name: str) -> str:
return model return model
if "/" in model: if "/" in model:
prefix = model.split("/", 1)[0].lower() return model
if prefix in _SDK_PREFIXES:
return model
return f"litellm/{model}"
lower = model.lower() lower = model.lower()
if lower.startswith("claude"): if lower.startswith("claude"):
return f"litellm/anthropic/{model}" return f"anthropic/{model}"
if lower.startswith("gemini"): if lower.startswith("gemini"):
return f"litellm/gemini/{model}" return f"gemini/{model}"
return model return model
@@ -116,7 +135,7 @@ def normalize_model_name(model_name: str) -> str:
def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bool: def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bool:
"""Return whether the resolved SDK route can only receive JSON function tools.""" """Return whether the resolved SDK route can only receive JSON function tools."""
model = model_name.strip().lower() model = model_name.strip().lower()
if model.startswith(("litellm/", "any-llm/")): if "/" in model and not model.startswith("openai/"):
return True return True
return bool(settings.llm.api_base) return bool(settings.llm.api_base)
+2
View File
@@ -15,6 +15,7 @@ from agents.sandbox import SandboxRunConfig
from strix.agents.factory import build_strix_agent, make_child_factory from strix.agents.factory import build_strix_agent, make_child_factory
from strix.config import load_settings from strix.config import load_settings
from strix.config.models import ( from strix.config.models import (
StrixProvider,
configure_sdk_model_defaults, configure_sdk_model_defaults,
normalize_model_name, normalize_model_name,
uses_chat_completions_tool_schema, uses_chat_completions_tool_schema,
@@ -159,6 +160,7 @@ async def run_strix_scan(
) )
run_config = RunConfig( run_config = RunConfig(
model=resolved_model, model=resolved_model,
model_provider=StrixProvider(),
model_settings=model_settings, model_settings=model_settings,
sandbox=SandboxRunConfig(client=bundle["client"], session=bundle["session"]), sandbox=SandboxRunConfig(client=bundle["client"], session=bundle["session"]),
trace_include_sensitive_data=False, trace_include_sensitive_data=False,
+7 -7
View File
@@ -13,7 +13,6 @@ from pathlib import Path
from agents.model_settings import ModelSettings from agents.model_settings import ModelSettings
from agents.models.interface import ModelTracing from agents.models.interface import ModelTracing
from agents.models.multi_provider import MultiProvider
from docker.errors import DockerException from docker.errors import DockerException
from rich.console import Console from rich.console import Console
from rich.panel import Panel from rich.panel import Panel
@@ -24,7 +23,7 @@ from strix.config import (
load_settings, load_settings,
persist_current, persist_current,
) )
from strix.config.models import configure_sdk_model_defaults, normalize_model_name from strix.config.models import StrixProvider, configure_sdk_model_defaults, normalize_model_name
from strix.core.paths import run_dir_for, runtime_state_dir from strix.core.paths import run_dir_for, runtime_state_dir
from strix.interface.cli import run_cli from strix.interface.cli import run_cli
from strix.interface.tui import run_tui from strix.interface.tui import run_tui
@@ -99,7 +98,8 @@ def validate_environment() -> None:
error_text.append("", style="white") error_text.append("", style="white")
error_text.append("STRIX_LLM", style="bold cyan") error_text.append("STRIX_LLM", style="bold cyan")
error_text.append( error_text.append(
" - Model name to use (e.g., 'gpt-5.4' or 'claude-sonnet-4-6')\n", " - Model name to use (e.g., 'openai/gpt-5.4' or "
"'anthropic/claude-opus-4-7')\n",
style="white", style="white",
) )
@@ -138,7 +138,7 @@ def validate_environment() -> None:
) )
error_text.append("\nExample setup:\n", style="white") error_text.append("\nExample setup:\n", style="white")
error_text.append("export STRIX_LLM='gpt-5.4'\n", style="dim white") error_text.append("export STRIX_LLM='openai/gpt-5.4'\n", style="dim white")
if missing_optional_vars: if missing_optional_vars:
for var in missing_optional_vars: for var in missing_optional_vars:
@@ -216,7 +216,7 @@ async def warm_up_llm() -> None:
configure_sdk_model_defaults(settings) configure_sdk_model_defaults(settings)
llm = settings.llm llm = settings.llm
model = MultiProvider().get_model(normalize_model_name(llm.model or "")) model = StrixProvider().get_model(normalize_model_name(llm.model or ""))
await asyncio.wait_for( await asyncio.wait_for(
model.get_response( model.get_response(
system_instructions="You are a helpful assistant.", system_instructions="You are a helpful assistant.",
@@ -256,8 +256,8 @@ async def warm_up_llm() -> None:
f"\n\nHint: '{raw_model}' has no provider prefix, so the SDK " f"\n\nHint: '{raw_model}' has no provider prefix, so the SDK "
f"routed it through OpenAI by default. For non-OpenAI providers " f"routed it through OpenAI by default. For non-OpenAI providers "
f"use the '<provider>/<model>' form, e.g. " f"use the '<provider>/<model>' form, e.g. "
f"'deepseek/deepseek-chat', 'groq/llama-3.1-70b', " f"'anthropic/claude-opus-4-7', 'deepseek/deepseek-reasoner', "
f"'anthropic/claude-3-5-sonnet'.", f"'openai/gpt-5.4'.",
style="yellow", style="yellow",
) )