From 879c686ab15ac3d4936a7ae5ba4adc46483576fe Mon Sep 17 00:00:00 2001 From: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com> Date: Wed, 14 Oct 2026 03:07:52 +0200 Subject: [PATCH 1/3] feat(sglang): map reasoning_effort to per-level thinking budgets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Go side already forwards the request's effort as Metadata["reasoning_effort"], but the sglang backend only read enable_thinking: every level got the same model-level thinking_budget, so low/medium/high produced identical reasoning. - New model option `effort_budgets:low=…;medium=…;high=…` picks the budget of the request's level; unlisted levels and requests without an effort keep `thinking_budget`. Parsing reuses _parse_thinking_budget. - A level listed in effort_budgets turns reasoning on for that request, so levels the Go side does not know cannot silently disable it. - ApplyReasoningEffort now treats `xhigh` and `max` as thinking levels. Before, they left DisableReasoning unset, so with a model default of "off" asking for more reasoning produced none. Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com> --- backend/python/sglang/backend.py | 65 +++++++++++++++++++- backend/python/sglang/test.py | 61 ++++++++++++++++++ core/config/model_config.go | 2 +- core/config/reasoning_effort_test.go | 10 +++ docs/content/advanced/model-configuration.md | 4 +- docs/content/features/text-generation.md | 13 ++++ 6 files changed, 149 insertions(+), 6 deletions(-) diff --git a/backend/python/sglang/backend.py b/backend/python/sglang/backend.py index 7936ddcf2a2b..2f988b6e62b1 100644 --- a/backend/python/sglang/backend.py +++ b/backend/python/sglang/backend.py @@ -122,6 +122,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer): # tests that construct it directly) doesn't AttributeError in # _build_sampling_params. thinking_budget: Optional[int] = None + effort_budgets: Optional[dict] = None # {effort level: tokens}, from `effort_budgets` reasoning_default: Optional[str] = None def _parse_options(self, options_list) -> Dict[str, str]: @@ -165,6 +166,44 @@ def _parse_thinking_budget(value) -> Optional[int]: return None return int(number) + @classmethod + def _parse_effort_budgets(cls, value) -> dict: + """Parse `effort_budgets` ("low=512;medium=2048;high=8192") into {level: tokens}. + + `;` separates entries because the YAML `options:` list already uses `:` + between option name and value. Invalid entries are skipped with a warning + (same policy as `thinking_budget`: never fail LoadModel over an option). + """ + budgets: dict = {} + if value is None or str(value).strip() == "": + return budgets + for entry in str(value).split(";"): + entry = entry.strip() + if not entry: + continue + level, sep, raw = entry.partition("=") + level = level.strip().lower() + if not sep or not level: + print(f"effort_budgets entry {entry!r} is not level=tokens, ignoring it", file=sys.stderr) + continue + tokens = cls._parse_thinking_budget(raw) + if tokens is not None: + budgets[level] = tokens + return budgets + + @staticmethod + def _effort(request) -> str: + """The request's reasoning_effort as forwarded by the Go side, lower-cased.""" + metadata = getattr(request, "Metadata", None) or {} + return (metadata.get("reasoning_effort") or "").strip().lower() + + def _thinking_budget_for(self, request) -> Optional[int]: + """Budget for this request: its effort level's entry, else the model default.""" + effort = self._effort(request) + if effort and self.effort_budgets and effort in self.effort_budgets: + return self.effort_budgets[effort] + return self.thinking_budget + @staticmethod def _strict_thinking_warning(thinking_budget: Optional[int], engine_kwargs: dict) -> Optional[str]: """sglang only enforces the budget with enable_strict_thinking on; without @@ -304,6 +343,16 @@ async def LoadModel(self, request, context): opts.get("thinking_budget") ) + # Per-effort budgets, e.g. `effort_budgets:low=512;medium=2048;high=8192`. + # The Go side already forwards the request's `reasoning_effort` as + # Metadata["reasoning_effort"], but until now nothing in this backend + # read it: every effort level got the same fixed `thinking_budget`, so + # low/medium/high produced identical reasoning. With this map a request + # gets the budget of its effort level; levels absent from the map, and + # requests without an effort, keep `thinking_budget`. Same semantics as + # vLLM's proposed `--reasoning-effort-budgets` (vllm-project/vllm#55432). + self.effort_budgets: dict = self._parse_effort_budgets(opts.get("effort_budgets")) + # Model-level default for whether the chat template opens a reasoning # block, as "off" or "on". Rides the same `options:` mechanism as # thinking_budget above. @@ -338,7 +387,9 @@ async def LoadModel(self, request, context): print(f"engine_args error: {err}", file=sys.stderr) return backend_pb2.Result(success=False, message=str(err)) - warning = self._strict_thinking_warning(self.thinking_budget, engine_kwargs) + warning = self._strict_thinking_warning( + self.thinking_budget or max((self.effort_budgets or {}).values(), default=None), engine_kwargs + ) if warning: print(warning, file=sys.stderr) @@ -457,8 +508,9 @@ def _build_sampling_params(self, request) -> dict: except json.JSONDecodeError: sampling_params["ebnf"] = grammar - if self.thinking_budget is not None: - sampling_params["custom_params"] = {"thinking_budget": self.thinking_budget} + budget = self._thinking_budget_for(request) + if budget is not None: + sampling_params["custom_params"] = {"thinking_budget": budget} return sampling_params @@ -473,6 +525,13 @@ def _thinking_default(self, request) -> Optional[bool]: wanted = request.Metadata.get("enable_thinking", "").lower() if wanted in ("true", "false"): return wanted == "true" + # An effort level listed in `effort_budgets` is an explicit request to + # think. The Go side only sets enable_thinking for the levels it knows + # (minimal/low/medium/high); for others such as `xhigh` or `max` it sets + # nothing, so `reasoning_default: off` would win and asking for MORE + # reasoning would yield NONE. + if self.effort_budgets and self._effort(request) in self.effort_budgets: + return True if self.reasoning_default == "off": return False if self.reasoning_default == "on": diff --git a/backend/python/sglang/test.py b/backend/python/sglang/test.py index 1b1a10d234ea..045e853cfc13 100644 --- a/backend/python/sglang/test.py +++ b/backend/python/sglang/test.py @@ -294,6 +294,67 @@ def test_no_thinking_budget_means_no_custom_params_key(self): params = servicer._build_sampling_params(request) self.assertNotIn("custom_params", params) + def _sampling_request(self, metadata=None): + from types import SimpleNamespace + + return SimpleNamespace( + Temperature=0.7, N=0, PresencePenalty=0, FrequencyPenalty=0, + RepetitionPenalty=0, TopP=0, TopK=0, MinP=0, Seed=0, + StopPrompts=[], StopTokenIds=[], IgnoreEOS=False, Tokens=0, + MinTokens=0, SkipSpecialTokens=False, Grammar="", + Metadata=metadata or {}, + ) + + def test_effort_budgets_parse(self): + """`low=512;medium=2048;high=8192` -> per-level dict; bad entries are skipped.""" + servicer = self._servicer() + self.assertEqual( + servicer._parse_effort_budgets("low=512; Medium=2048.0;high=8192"), + {"low": 512, "medium": 2048, "high": 8192}, + ) + self.assertEqual(servicer._parse_effort_budgets("low=abc;medium;=5;high=0;xhigh=100"), {"xhigh": 100}) + self.assertEqual(servicer._parse_effort_budgets(None), {}) + self.assertEqual(servicer._parse_effort_budgets(""), {}) + + def test_effort_level_selects_its_budget(self): + """The request's reasoning_effort (Metadata, forwarded by the Go side) + picks its own budget, so low/medium/high stop being identical.""" + servicer = self._servicer() + servicer.thinking_budget = 256 + servicer.effort_budgets = {"low": 512, "medium": 2048, "high": 8192} + for level, budget in (("low", 512), ("medium", 2048), ("HIGH", 8192)): + params = servicer._build_sampling_params(self._sampling_request({"reasoning_effort": level})) + self.assertEqual(params["custom_params"], {"thinking_budget": budget}, level) + + def test_listed_effort_turns_thinking_on_despite_default_off(self): + """`xhigh`/`max` get no enable_thinking from the Go side; if the level is + in effort_budgets it must still think instead of falling to default off.""" + servicer = self._servicer() + servicer.reasoning_default = "off" + servicer.effort_budgets = {"xhigh": 16384} + self.assertTrue(servicer._thinking_default(_request(metadata={"reasoning_effort": "xhigh"}))) + self.assertFalse(servicer._thinking_default(_request(metadata={"reasoning_effort": "ultra"}))) + self.assertFalse(servicer._thinking_default( + _request(metadata={"reasoning_effort": "xhigh", "enable_thinking": "false"}))) + + def test_effort_without_entry_falls_back_to_thinking_budget(self): + servicer = self._servicer() + servicer.thinking_budget = 256 + servicer.effort_budgets = {"high": 8192} + for metadata in ({}, {"reasoning_effort": "low"}, {"reasoning_effort": ""}): + params = servicer._build_sampling_params(self._sampling_request(metadata)) + self.assertEqual(params["custom_params"], {"thinking_budget": 256}, metadata) + + def test_effort_budgets_alone_without_default(self): + """Only levels in the map get a budget; others stay unconfigured (no key).""" + servicer = self._servicer() + servicer.effort_budgets = {"low": 512} + self.assertEqual( + servicer._build_sampling_params(self._sampling_request({"reasoning_effort": "low"}))["custom_params"], + {"thinking_budget": 512}, + ) + self.assertNotIn("custom_params", servicer._build_sampling_params(self._sampling_request({"reasoning_effort": "high"}))) + def test_thinking_budget_accepts_integral_spellings(self): """YAML options arrive as strings; "512" and "512.0" both mean 512.""" servicer = self._servicer() diff --git a/core/config/model_config.go b/core/config/model_config.go index a52618fd2351..e13b27655cea 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -1142,7 +1142,7 @@ func (c *ModelConfig) ApplyReasoningEffort(requestEffort string) { case "none": disable := true c.ReasoningConfig.DisableReasoning = &disable - case "minimal", "low", "medium", "high": + case "minimal", "low", "medium", "high", "xhigh", "max": if c.ReasoningConfig.DisableReasoning == nil || !*c.ReasoningConfig.DisableReasoning { enable := false c.ReasoningConfig.DisableReasoning = &enable diff --git a/core/config/reasoning_effort_test.go b/core/config/reasoning_effort_test.go index 8f23ba6906fd..c3a341eb2fae 100644 --- a/core/config/reasoning_effort_test.go +++ b/core/config/reasoning_effort_test.go @@ -35,6 +35,16 @@ var _ = Describe("ModelConfig.ApplyReasoningEffort", func() { Expect(*c.ReasoningConfig.DisableReasoning).To(BeFalse()) }) + It("enables thinking for the levels above high", func() { + for _, level := range []string{"xhigh", "max"} { + c := &config.ModelConfig{} + c.ApplyReasoningEffort(level) + Expect(c.ReasoningEffort).To(Equal(level)) + Expect(c.ReasoningConfig.DisableReasoning).ToNot(BeNil()) + Expect(*c.ReasoningConfig.DisableReasoning).To(BeFalse()) + } + }) + It("does not let a level override an operator's config-level disable", func() { disabled := true c := &config.ModelConfig{} diff --git a/docs/content/advanced/model-configuration.md b/docs/content/advanced/model-configuration.md index 81bbacaed2b7..271500c80599 100644 --- a/docs/content/advanced/model-configuration.md +++ b/docs/content/advanced/model-configuration.md @@ -646,7 +646,7 @@ These load-time options control how the backend parses `` reasoning block This is the load-time reasoning configuration. The orthogonal per-request `enable_thinking` chat-template kwarg toggles thinking on/off per call without restarting the model. It can be driven either by the YAML `reasoning.disable` field (model default) or per request via the OpenAI `reasoning_effort` field on `/v1/chat/completions`: - `reasoning_effort: "none"` disables thinking for that request (`enable_thinking=false`) - useful to run a single reasoning model like Qwen3 for low-latency tasks while still enabling reasoning on other requests. -- `reasoning_effort: "minimal" | "low" | "medium" | "high"` enables thinking, unless the model config explicitly set `reasoning.disable: true` (an operator's explicit disable wins and is never re-enabled by a request). +- `reasoning_effort: "minimal" | "low" | "medium" | "high" | "xhigh" | "max"` enables thinking, unless the model config explicitly set `reasoning.disable: true` (an operator's explicit disable wins and is never re-enabled by a request). {{% /notice %}} #### `reasoning_effort` as a chat-template kwarg @@ -657,7 +657,7 @@ Set a per-model default in the config so every request inherits it (a per-reques ```yaml name: my-model -reasoning_effort: none # none | minimal | low | medium | high +reasoning_effort: none # none | minimal | low | medium | high | xhigh | max ``` For [realtime pipelines]({{%relref "features/openai-realtime" %}}), set it on the pipeline so it applies to the pipeline's LLM without editing that model's own config: diff --git a/docs/content/features/text-generation.md b/docs/content/features/text-generation.md index bd9236e9e017..1ea9837bd2ec 100644 --- a/docs/content/features/text-generation.md +++ b/docs/content/features/text-generation.md @@ -1016,6 +1016,19 @@ LocalAI warns if you configure a budget without that engine option. Keep the budget well below the `max_tokens` of your requests: if `max_tokens` is reached first, the budget never triggers and the whole reply can be spent on reasoning, leaving the answer empty. +`effort_budgets` maps the request's `reasoning_effort` to its own budget, so the levels differ in how long the model may think: + +```yaml +options: + - thinking_budget:256 # fallback: no effort, or a level not listed below + - effort_budgets:low=256;medium=1024;high=4096;xhigh=16384;max=16384 +``` + +Entries are separated by `;` and use the same parsing rules as `thinking_budget` (invalid entries are skipped with a warning). +A request whose effort is not listed falls back to `thinking_budget`; without either, no budget is sent. +A level listed here also turns reasoning on for that request, even when `reasoning_default:off` is set. +The budget is an upper bound, not a target: the model may stop thinking earlier. + `reasoning_default:on` or `reasoning_default:off` sets the default for LocalAI's tokenizer chat template. Request metadata `enable_thinking` set to `"true"` or `"false"` overrides this default. An explicit prompt bypasses tokenizer template rendering. From 0bd723a65285a710c1355fad8776f8c43c0b8b26 Mon Sep 17 00:00:00 2001 From: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com> Date: Wed, 14 Oct 2026 03:28:24 +0200 Subject: [PATCH 2/3] fix(sglang): review follow-ups for effort_budgets - warn on an effort_budgets entry without a token count (high=) - update comments: enable_thinking=false still wins over a listed level - document that precedence in text-generation.md - cover xhigh/max and the empty-entry warning in the tests Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com> --- backend/python/sglang/backend.py | 18 +++++++++++------- backend/python/sglang/test.py | 23 ++++++++++++++++++----- docs/content/features/text-generation.md | 2 +- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/backend/python/sglang/backend.py b/backend/python/sglang/backend.py index 2f988b6e62b1..bb853af9a179 100644 --- a/backend/python/sglang/backend.py +++ b/backend/python/sglang/backend.py @@ -186,6 +186,9 @@ def _parse_effort_budgets(cls, value) -> dict: if not sep or not level: print(f"effort_budgets entry {entry!r} is not level=tokens, ignoring it", file=sys.stderr) continue + if not raw.strip(): + print(f"effort_budgets entry {entry!r} has no token count, ignoring it", file=sys.stderr) + continue tokens = cls._parse_thinking_budget(raw) if tokens is not None: budgets[level] = tokens @@ -349,8 +352,7 @@ async def LoadModel(self, request, context): # read it: every effort level got the same fixed `thinking_budget`, so # low/medium/high produced identical reasoning. With this map a request # gets the budget of its effort level; levels absent from the map, and - # requests without an effort, keep `thinking_budget`. Same semantics as - # vLLM's proposed `--reasoning-effort-budgets` (vllm-project/vllm#55432). + # requests without an effort, keep `thinking_budget`. self.effort_budgets: dict = self._parse_effort_budgets(opts.get("effort_budgets")) # Model-level default for whether the chat template opens a reasoning @@ -517,7 +519,8 @@ def _build_sampling_params(self, request) -> dict: def _thinking_default(self, request) -> Optional[bool]: """Whether this request should render with reasoning on, off, or unset. - Per-request ``Metadata["enable_thinking"]`` wins; the model-level + Per-request ``Metadata["enable_thinking"]`` wins; next, an effort level + listed in ``effort_budgets`` turns reasoning on; the model-level ``reasoning_default`` option fills in when the request is silent. Returns None when neither says anything, leaving template behaviour untouched. @@ -526,10 +529,11 @@ def _thinking_default(self, request) -> Optional[bool]: if wanted in ("true", "false"): return wanted == "true" # An effort level listed in `effort_budgets` is an explicit request to - # think. The Go side only sets enable_thinking for the levels it knows - # (minimal/low/medium/high); for others such as `xhigh` or `max` it sets - # nothing, so `reasoning_default: off` would win and asking for MORE - # reasoning would yield NONE. + # think. Normally the Go side already sends enable_thinking for known + # levels; this covers levels it does not set it for (custom names in + # the map, or callers that only send reasoning_effort), where + # `reasoning_default: off` would otherwise win. An explicit + # enable_thinking=false above still takes precedence. if self.effort_budgets and self._effort(request) in self.effort_budgets: return True if self.reasoning_default == "off": diff --git a/backend/python/sglang/test.py b/backend/python/sglang/test.py index 045e853cfc13..c04c2156f4c3 100644 --- a/backend/python/sglang/test.py +++ b/backend/python/sglang/test.py @@ -315,24 +315,37 @@ def test_effort_budgets_parse(self): self.assertEqual(servicer._parse_effort_budgets("low=abc;medium;=5;high=0;xhigh=100"), {"xhigh": 100}) self.assertEqual(servicer._parse_effort_budgets(None), {}) self.assertEqual(servicer._parse_effort_budgets(""), {}) + self.assertEqual(servicer._parse_effort_budgets(";;low=512;"), {"low": 512}) + + def test_effort_budgets_empty_value_warns(self): + """`high=` is skipped like any other invalid entry, and says so.""" + import contextlib + import io + + servicer = self._servicer() + err = io.StringIO() + with contextlib.redirect_stderr(err): + self.assertEqual(servicer._parse_effort_budgets("high=;max=16384"), {"max": 16384}) + self.assertIn("'high='", err.getvalue()) def test_effort_level_selects_its_budget(self): """The request's reasoning_effort (Metadata, forwarded by the Go side) picks its own budget, so low/medium/high stop being identical.""" servicer = self._servicer() servicer.thinking_budget = 256 - servicer.effort_budgets = {"low": 512, "medium": 2048, "high": 8192} - for level, budget in (("low", 512), ("medium", 2048), ("HIGH", 8192)): + servicer.effort_budgets = {"low": 512, "medium": 2048, "high": 8192, "xhigh": 16384, "max": 32768} + for level, budget in (("low", 512), ("medium", 2048), ("HIGH", 8192), ("xhigh", 16384), ("max", 32768)): params = servicer._build_sampling_params(self._sampling_request({"reasoning_effort": level})) self.assertEqual(params["custom_params"], {"thinking_budget": budget}, level) def test_listed_effort_turns_thinking_on_despite_default_off(self): - """`xhigh`/`max` get no enable_thinking from the Go side; if the level is - in effort_budgets it must still think instead of falling to default off.""" + """A request that carries only reasoning_effort (no enable_thinking) for a + level listed in effort_budgets must think instead of falling to default off.""" servicer = self._servicer() servicer.reasoning_default = "off" - servicer.effort_budgets = {"xhigh": 16384} + servicer.effort_budgets = {"xhigh": 16384, "max": 16384} self.assertTrue(servicer._thinking_default(_request(metadata={"reasoning_effort": "xhigh"}))) + self.assertTrue(servicer._thinking_default(_request(metadata={"reasoning_effort": "max"}))) self.assertFalse(servicer._thinking_default(_request(metadata={"reasoning_effort": "ultra"}))) self.assertFalse(servicer._thinking_default( _request(metadata={"reasoning_effort": "xhigh", "enable_thinking": "false"}))) diff --git a/docs/content/features/text-generation.md b/docs/content/features/text-generation.md index 1ea9837bd2ec..8cae3d6d8e4a 100644 --- a/docs/content/features/text-generation.md +++ b/docs/content/features/text-generation.md @@ -1026,7 +1026,7 @@ options: Entries are separated by `;` and use the same parsing rules as `thinking_budget` (invalid entries are skipped with a warning). A request whose effort is not listed falls back to `thinking_budget`; without either, no budget is sent. -A level listed here also turns reasoning on for that request, even when `reasoning_default:off` is set. +A level listed here also turns reasoning on for that request, even when `reasoning_default:off` is set; an explicit `enable_thinking: "false"` in the request metadata (also sent when the model sets `reasoning.disable: true`) still wins. The budget is an upper bound, not a target: the model may stop thinking earlier. `reasoning_default:on` or `reasoning_default:off` sets the default for LocalAI's tokenizer chat template. From 45a960ca1fc21f73cf89772fc75cedf1d6fa3558 Mon Sep 17 00:00:00 2001 From: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com> Date: Wed, 14 Oct 2026 04:02:59 +0200 Subject: [PATCH 3/3] test: cover config-level disable for xhigh/max and the effort-only load warning - ApplyReasoningEffort keeps an operator's reasoning.disable for high, xhigh and max - sglang: _configured_budget() feeds the strict-thinking warning; tested with effort_budgets alone Signed-off-by: pos-ei-don <1822533+pos-ei-don@users.noreply.github.com> --- backend/python/sglang/backend.py | 8 +++++--- backend/python/sglang/test.py | 10 ++++++++++ core/config/reasoning_effort_test.go | 12 +++++++----- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/backend/python/sglang/backend.py b/backend/python/sglang/backend.py index bb853af9a179..bacb401595f0 100644 --- a/backend/python/sglang/backend.py +++ b/backend/python/sglang/backend.py @@ -207,6 +207,10 @@ def _thinking_budget_for(self, request) -> Optional[int]: return self.effort_budgets[effort] return self.thinking_budget + def _configured_budget(self) -> Optional[int]: + """Any budget configured for this model (fixed or per effort), for the load-time warning.""" + return self.thinking_budget or max((self.effort_budgets or {}).values(), default=None) + @staticmethod def _strict_thinking_warning(thinking_budget: Optional[int], engine_kwargs: dict) -> Optional[str]: """sglang only enforces the budget with enable_strict_thinking on; without @@ -389,9 +393,7 @@ async def LoadModel(self, request, context): print(f"engine_args error: {err}", file=sys.stderr) return backend_pb2.Result(success=False, message=str(err)) - warning = self._strict_thinking_warning( - self.thinking_budget or max((self.effort_budgets or {}).values(), default=None), engine_kwargs - ) + warning = self._strict_thinking_warning(self._configured_budget(), engine_kwargs) if warning: print(warning, file=sys.stderr) diff --git a/backend/python/sglang/test.py b/backend/python/sglang/test.py index c04c2156f4c3..05c967a0fdfb 100644 --- a/backend/python/sglang/test.py +++ b/backend/python/sglang/test.py @@ -402,6 +402,16 @@ def test_warns_when_budget_set_without_strict_thinking(self): self.assertIsNone(servicer._strict_thinking_warning(512, {"enable_strict_thinking": True})) self.assertIsNone(servicer._strict_thinking_warning(None, {})) + def test_warns_when_only_effort_budgets_are_set(self): + """A model with effort_budgets but no thinking_budget still needs strict thinking.""" + servicer = self._servicer() + servicer.thinking_budget = None + servicer.effort_budgets = {"low": 256, "max": 16384} + self.assertEqual(servicer._configured_budget(), 16384) + self.assertIn("enable_strict_thinking", servicer._strict_thinking_warning(servicer._configured_budget(), {})) + servicer.effort_budgets = {} + self.assertIsNone(servicer._configured_budget()) + def test_explicit_zero_temperature_and_seed_are_preserved(self): """Temperature=0 is greedy decoding and 0 is a valid seed — neither is an unset value. A dropped seed turns a reproducible request random.""" diff --git a/core/config/reasoning_effort_test.go b/core/config/reasoning_effort_test.go index c3a341eb2fae..4cf7362d2a8b 100644 --- a/core/config/reasoning_effort_test.go +++ b/core/config/reasoning_effort_test.go @@ -46,11 +46,13 @@ var _ = Describe("ModelConfig.ApplyReasoningEffort", func() { }) It("does not let a level override an operator's config-level disable", func() { - disabled := true - c := &config.ModelConfig{} - c.ReasoningConfig.DisableReasoning = &disabled - c.ApplyReasoningEffort("high") - Expect(*c.ReasoningConfig.DisableReasoning).To(BeTrue()) + for _, level := range []string{"high", "xhigh", "max"} { + disabled := true + c := &config.ModelConfig{} + c.ReasoningConfig.DisableReasoning = &disabled + c.ApplyReasoningEffort(level) + Expect(*c.ReasoningConfig.DisableReasoning).To(BeTrue(), level) + } }) It("is a no-op on the toggle when no effort is set anywhere", func() {