Pre-warm-up unknown-model warning + LiteLLM streaming hardening

Warn on bare unknown model names before warm-up. is_known_openai_bare_model
consults litellm.model_cost and matches only entries whose
litellm_provider == "openai". When the configured STRIX_LLM has no
provider prefix, isn't a known OpenAI model, and no LLM_API_BASE is
set, show a clear panel pointing the user at the <provider>/<model>
form and exit before issuing the doomed request — no more chasing an
"Incorrect API key" 401 from OpenAI when the user actually meant
deepseek/, anthropic/, etc. Custom-base configs are still allowed
through unconfirmed.

Disable LiteLLM's message-logging and streaming-logging knobs to cut
noise and skip one of the two end-of-stream submit paths. The other
path at streaming_handler.py:2206 schedules work on a global
ThreadPoolExecutor that loses to atexit shutdown when the interpreter
is winding down; the SDK's stream consumer surfaces that as a fatal
"cannot schedule new futures after shutdown" RuntimeError even though
the actual stream content was already delivered. Catch and swallow
that specific RuntimeError in _run_cycle so the scan isn't killed by
an upstream end-of-stream logging race.
This commit is contained in:
0xallam
2026-06-08 02:12:56 +03:00
committed by Ahmed Allam
parent 3665a7899f
commit 143b9e7040
3 changed files with 65 additions and 9 deletions
+13 -1
View File
@@ -95,11 +95,13 @@ def _mirror_api_key_to_provider_env(model_name: str | None, api_key: str) -> Non
def _configure_litellm_compatibility() -> None:
"""Enable LiteLLM's permissive param-handling mode."""
"""Enable LiteLLM's permissive param handling and disable its callbacks."""
import litellm
litellm.drop_params = True
litellm.modify_params = True
litellm.turn_off_message_logging = True
litellm.disable_streaming_logging = True
def _configure_litellm_default(name: str, value: str) -> None:
@@ -115,3 +117,13 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo
if "/" in model and not model.startswith("openai/"):
return True
return bool(settings.llm.api_base)
def is_known_openai_bare_model(model_name: str) -> bool:
import litellm
name = model_name.strip().lower()
if not name or "/" in name:
return False
entry = litellm.model_cost.get(name)
return bool(entry and entry.get("litellm_provider") == "openai")
+14 -6
View File
@@ -349,12 +349,20 @@ async def _run_cycle( # noqa: PLR0912
)
await coordinator.attach_stream(agent_id, stream)
try:
async for event in stream.stream_events():
if event_sink is not None:
try:
event_sink(agent_id, event)
except Exception:
logger.exception("stream event sink failed for %s", agent_id)
try:
async for event in stream.stream_events():
if event_sink is not None:
try:
event_sink(agent_id, event)
except Exception:
logger.exception("stream event sink failed for %s", agent_id)
except RuntimeError as stream_exc:
if "after shutdown" not in str(stream_exc):
raise
logger.warning(
"Ignoring LiteLLM end-of-stream shutdown race for %s",
agent_id,
)
if stream.run_loop_exception is not None:
raise stream.run_loop_exception
finally:
+38 -2
View File
@@ -22,7 +22,11 @@ from strix.config import (
load_settings,
persist_current,
)
from strix.config.models import StrixProvider, configure_sdk_model_defaults
from strix.config.models import (
StrixProvider,
configure_sdk_model_defaults,
is_known_openai_bare_model,
)
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
@@ -215,7 +219,39 @@ async def warm_up_llm() -> None:
configure_sdk_model_defaults(settings)
llm = settings.llm
model = StrixProvider().get_model((llm.model or "").strip())
raw_model = (llm.model or "").strip()
if (
raw_model
and "/" not in raw_model
and not is_known_openai_bare_model(raw_model)
and not llm.api_base
):
warn_text = Text()
warn_text.append("UNKNOWN MODEL NAME", style="bold yellow")
warn_text.append("\n\n", style="white")
warn_text.append(f"'{raw_model}'", style="bold cyan")
warn_text.append(
" is not a known OpenAI model. Bare names route to OpenAI by default.\n"
"If you meant a non-OpenAI provider, use the '",
style="white",
)
warn_text.append("<provider>/<model>", style="bold cyan")
warn_text.append(
"' form, e.g. 'anthropic/claude-opus-4-7', 'deepseek/deepseek-v4-pro'.",
style="white",
)
console.print(
Panel(
warn_text,
title="[bold white]STRIX",
title_align="left",
border_style="yellow",
padding=(1, 2),
),
)
sys.exit(1)
model = StrixProvider().get_model(raw_model)
await asyncio.wait_for(
model.get_response(
system_instructions="You are a helpful assistant.",