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:
+26
-7
@@ -6,6 +6,7 @@ import os
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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 (
|
||||
ModelRetryBackoffSettings,
|
||||
ModelRetrySettings,
|
||||
@@ -14,10 +15,31 @@ from agents.retry import (
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agents.models.interface import ModelProvider
|
||||
|
||||
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(
|
||||
@@ -99,16 +121,13 @@ def normalize_model_name(model_name: str) -> str:
|
||||
return model
|
||||
|
||||
if "/" in model:
|
||||
prefix = model.split("/", 1)[0].lower()
|
||||
if prefix in _SDK_PREFIXES:
|
||||
return model
|
||||
return f"litellm/{model}"
|
||||
|
||||
lower = model.lower()
|
||||
if lower.startswith("claude"):
|
||||
return f"litellm/anthropic/{model}"
|
||||
return f"anthropic/{model}"
|
||||
if lower.startswith("gemini"):
|
||||
return f"litellm/gemini/{model}"
|
||||
return f"gemini/{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:
|
||||
"""Return whether the resolved SDK route can only receive JSON function tools."""
|
||||
model = model_name.strip().lower()
|
||||
if model.startswith(("litellm/", "any-llm/")):
|
||||
if "/" in model and not model.startswith("openai/"):
|
||||
return True
|
||||
return bool(settings.llm.api_base)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from agents.sandbox import SandboxRunConfig
|
||||
from strix.agents.factory import build_strix_agent, make_child_factory
|
||||
from strix.config import load_settings
|
||||
from strix.config.models import (
|
||||
StrixProvider,
|
||||
configure_sdk_model_defaults,
|
||||
normalize_model_name,
|
||||
uses_chat_completions_tool_schema,
|
||||
@@ -159,6 +160,7 @@ async def run_strix_scan(
|
||||
)
|
||||
run_config = RunConfig(
|
||||
model=resolved_model,
|
||||
model_provider=StrixProvider(),
|
||||
model_settings=model_settings,
|
||||
sandbox=SandboxRunConfig(client=bundle["client"], session=bundle["session"]),
|
||||
trace_include_sensitive_data=False,
|
||||
|
||||
@@ -13,7 +13,6 @@ from pathlib import Path
|
||||
|
||||
from agents.model_settings import ModelSettings
|
||||
from agents.models.interface import ModelTracing
|
||||
from agents.models.multi_provider import MultiProvider
|
||||
from docker.errors import DockerException
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
@@ -24,7 +23,7 @@ from strix.config import (
|
||||
load_settings,
|
||||
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.interface.cli import run_cli
|
||||
from strix.interface.tui import run_tui
|
||||
@@ -99,7 +98,8 @@ def validate_environment() -> None:
|
||||
error_text.append("• ", style="white")
|
||||
error_text.append("STRIX_LLM", style="bold cyan")
|
||||
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",
|
||||
)
|
||||
|
||||
@@ -138,7 +138,7 @@ def validate_environment() -> None:
|
||||
)
|
||||
|
||||
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:
|
||||
for var in missing_optional_vars:
|
||||
@@ -216,7 +216,7 @@ async def warm_up_llm() -> None:
|
||||
configure_sdk_model_defaults(settings)
|
||||
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(
|
||||
model.get_response(
|
||||
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"routed it through OpenAI by default. For non-OpenAI providers "
|
||||
f"use the '<provider>/<model>' form, e.g. "
|
||||
f"'deepseek/deepseek-chat', 'groq/llama-3.1-70b', "
|
||||
f"'anthropic/claude-3-5-sonnet'.",
|
||||
f"'anthropic/claude-opus-4-7', 'deepseek/deepseek-reasoner', "
|
||||
f"'openai/gpt-5.4'.",
|
||||
style="yellow",
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user