diff --git a/plugins/model-providers/upstage/__init__.py b/plugins/model-providers/upstage/__init__.py index 03e954e3e4ea..982e741f9e86 100644 --- a/plugins/model-providers/upstage/__init__.py +++ b/plugins/model-providers/upstage/__init__.py @@ -6,10 +6,12 @@ from providers import register_provider from providers.base import ProviderProfile -# Model-name markers for Solar families that accept ``reasoning_effort``. -# Substring match (not startswith) so dated variants like -# ``solar-pro3-250127`` and ``solar-open-...`` are covered too. -_REASONING_MODEL_MARKERS = ("solar-pro", "solar-open") +# Model-name markers for Solar families that do NOT accept ``reasoning_effort``. +# Deny-list on purpose: newly released Solar models are assumed +# reasoning-capable by default, so only the known non-reasoning families are +# listed here. Substring match (not startswith) so dated variants like +# ``solar-mini-250127`` are covered too. +_NON_REASONING_MODEL_MARKERS = ("solar-mini", "syn-pro") # When the user hasn't picked a reasoning effort, Hermes passes # reasoning_config=None. Solar's own server default is "minimal" (reasoning @@ -22,15 +24,20 @@ _DEFAULT_REASONING_EFFORT = "medium" def _model_supports_reasoning(model: str | None) -> bool: - """Solar reasoning-capable models. + """Solar reasoning-capable models — True unless the model is deny-listed. The Solar Pro family (``solar-pro``, ``solar-pro2``, ``solar-pro3`` and dated variants like ``solar-pro3-250127``) and the Solar Open family - (``solar-open*``) accept ``reasoning_effort``. ``solar-mini`` / ``syn-pro`` - ignore the parameter, so we don't send it. + (``solar-open*``) accept ``reasoning_effort``; only ``solar-mini`` / + ``syn-pro`` ignore the parameter, so we deny-list those and treat every + other (incl. future) Solar model as reasoning-capable. + + ``None``/empty model → True: the provider default (``fallback_models[0]``, + the ``solar-pro`` rolling alias) is reasoning-capable, so an unset model + gets the same default-on behaviour. """ m = (model or "").strip().lower() - return any(marker in m for marker in _REASONING_MODEL_MARKERS) + return not any(marker in m for marker in _NON_REASONING_MODEL_MARKERS) class UpstageProfile(ProviderProfile): @@ -54,7 +61,8 @@ class UpstageProfile(ProviderProfile): ) -> tuple[dict[str, Any], dict[str, Any]]: top_level: dict[str, Any] = {} - # solar-mini / syn-pro ignore reasoning_effort — send nothing. + # solar-mini / syn-pro (the deny-list) ignore reasoning_effort — send + # nothing. Everything else, including future Solar models, gets it. if not _model_supports_reasoning(model): return {}, top_level diff --git a/tests/plugins/model_providers/test_upstage_profile.py b/tests/plugins/model_providers/test_upstage_profile.py index 591408d71b76..e78c2326ee11 100644 --- a/tests/plugins/model_providers/test_upstage_profile.py +++ b/tests/plugins/model_providers/test_upstage_profile.py @@ -113,9 +113,9 @@ class TestUpstageReasoning: _, top_level = upstage_profile.build_api_kwargs_extras(model=model) assert top_level == {"reasoning_effort": "medium"} - @pytest.mark.parametrize("model", ["solar-mini", "syn-pro"]) - def test_no_config_non_pro_still_omits(self, upstage_profile, model): - # Default-on must not leak to non-reasoning models. + @pytest.mark.parametrize("model", ["solar-mini", "solar-mini-202610", "syn-pro"]) + def test_no_config_deny_listed_still_omits(self, upstage_profile, model): + # Default-on must not leak to the deny-listed non-reasoning models. _, top_level = upstage_profile.build_api_kwargs_extras(model=model) assert top_level == {} @@ -137,15 +137,36 @@ class TestUpstageReasoning: ) assert top_level == {"reasoning_effort": "high"} - @pytest.mark.parametrize("model", ["solar-mini", "syn-pro"]) - def test_non_pro_models_never_send_reasoning(self, upstage_profile, model): - # solar-mini / syn-pro ignore reasoning_effort, so never send it. + @pytest.mark.parametrize("model", ["solar-mini", "solar-mini-202610", "syn-pro"]) + def test_deny_listed_models_never_send_reasoning(self, upstage_profile, model): + # solar-mini / syn-pro ignore reasoning_effort, so never send it — + # even when the user explicitly enables reasoning. extra_body, top_level = upstage_profile.build_api_kwargs_extras( reasoning_config={"enabled": True, "effort": "high"}, model=model ) assert extra_body == {} assert top_level == {} + @pytest.mark.parametrize("model", ["solar-future", "solar-future-260601"]) + def test_unknown_future_models_default_to_reasoning(self, upstage_profile, model): + # Deny-list semantics: a future Solar model we've never heard of is + # assumed reasoning-capable, so reasoning_effort is sent instead of + # being silently dropped (the old allow-list failure mode). + _, top_level = upstage_profile.build_api_kwargs_extras( + reasoning_config={"enabled": True, "effort": "high"}, model=model + ) + assert top_level == {"reasoning_effort": "high"} + + # And the unset-config default-on path applies to it too. + _, top_level = upstage_profile.build_api_kwargs_extras(model=model) + assert top_level == {"reasoning_effort": "medium"} + + def test_none_model_defaults_to_reasoning(self, upstage_profile): + # No model in context → treated as reasoning-capable, consistent with + # the provider default (fallback_models[0] == "solar-pro"). + _, top_level = upstage_profile.build_api_kwargs_extras(model=None) + assert top_level == {"reasoning_effort": "medium"} + def upstage_profile_singleton(): import providers