diff --git a/gateway/platforms/api_server.py b/gateway/platforms/api_server.py index 57f943eda1..30ed5a8108 100644 --- a/gateway/platforms/api_server.py +++ b/gateway/platforms/api_server.py @@ -245,7 +245,15 @@ def _coerce_request_bool(value: Any, default: bool = False) -> bool: _REQUEST_OPTION_MISSING = object() -_REASONING_EFFORTS = frozenset({"none", "minimal", "low", "medium", "high", "xhigh"}) +# Full internal ladder + "none": the API server accepts what /reasoning and +# config.yaml accept (hermes_constants.VALID_REASONING_EFFORTS); wire-level +# clamping to each provider's vocabulary happens downstream in the +# transports/profiles via agent.reasoning_effort. Rejecting "max"/"ultra" +# here made API/browser clients second-class citizens of the ladder +# (#78216's api_server observation). +_REASONING_EFFORTS = frozenset( + {"none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"} +) _RUNTIME_AGENT_OVERRIDE_KEYS = ( "api_key", "base_url", diff --git a/tests/gateway/test_api_server_reasoning_ladder.py b/tests/gateway/test_api_server_reasoning_ladder.py new file mode 100644 index 0000000000..0af8770f8e --- /dev/null +++ b/tests/gateway/test_api_server_reasoning_ladder.py @@ -0,0 +1,33 @@ +"""API-server reasoning-effort request parsing accepts the full ladder. + +_request_reasoning_config() previously whitelisted only none..xhigh, so an +API/browser client sending ``max`` or ``ultra`` (both valid /reasoning and +config.yaml levels) was silently ignored and the session ran at the default +effort. The server now accepts the full internal ladder; per-provider wire +clamping happens downstream in the transports/profiles via +agent.reasoning_effort (#78216's api_server observation). +""" + +from gateway.platforms.api_server import _request_reasoning_config +from hermes_constants import VALID_REASONING_EFFORTS + + +class TestRequestReasoningFullLadder: + def test_every_configurable_level_is_accepted(self): + for level in VALID_REASONING_EFFORTS: + out = _request_reasoning_config({"reasoning_effort": level}) + assert out == {"enabled": True, "effort": level}, level + + def test_none_disables(self): + assert _request_reasoning_config({"reasoning_effort": "none"}) == { + "enabled": False + } + + def test_structured_object_shape(self): + out = _request_reasoning_config( + {"reasoning": {"enabled": True, "effort": "ultra"}} + ) + assert out == {"enabled": True, "effort": "ultra"} + + def test_unknown_level_still_ignored(self): + assert _request_reasoning_config({"reasoning_effort": "turbo"}) is None