refactor(agent): treat unknown Solar models as reasoning-capable

Invert the reasoning-support check from an allow-list (solar-pro,
solar-open) to a deny-list of the known non-reasoning families
(solar-mini, syn-pro). Newly released Solar models now get
reasoning_effort by default instead of having it silently dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Changhyun Min 2026-06-11 17:40:46 +09:00 committed by kshitij
parent 20502b407c
commit 0031c5c371
2 changed files with 44 additions and 15 deletions

View file

@ -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

View file

@ -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