sgl-project/sglang · error · ValueError

Reasoning parser ' ' is always-on and cannot be disabled…

Error message

Reasoning parser '{self.reasoning_parser}' is always-on and cannot be disabled via Anthropic thinking

What it means

The request tried to disable thinking (Anthropic thinking enabled=False) for a reasoning parser that SGLang classifies as always-on (e.g. its config special_case == "always" or the default mode resolves to "always"). Such models always produce reasoning and cannot be turned off, so disabling raises instead of silently failing.

Solutions

  1. Omit the thinking field entirely for always-on reasoning models
  2. Switch to a model whose reasoning is toggleable if disabling is a hard requirement
  3. Check the parser config's special_case/default mode to know which models are always-on

Example fix

# before
request.thinking = {"type": "disabled"}  # always-on parser
# after
request.thinking = None
Defensive patterns

Strategy: validation

Validate before calling

if thinking is not None and thinking.get('type') == 'disabled' and model_is_always_on_reasoner:
    thinking = None

Type guard

def is_always_on(parser_name) -> bool: return parser_name in ALWAYS_ON_PARSERS  # e.g. per docs

Try / catch

except ValueError as e: if 'always-on' in str(e): drop thinking param and resend

Prevention

When it happens

Trigger: Anthropic-style request with thinking disabled against an always-on reasoning model (certain hybrid/always-thinking model configs).

Common situations: Clients that send thinking={"type":"disabled"} by default for cost control; switching a client from a toggleable reasoner to an always-on one.

Related errors


AI-assisted analysis of sgl-project/sglang@0132848349 (2026-08-28). Data as JSON: /api/errors/d73c61a068ddab02. Report an issue: GitHub.

Appendix: source

Thrown at python/sglang/srt/entrypoints/openai/serving_chat.py:2399

            # Effort-conditioned, not toggled: "none" (0.0) is the off switch.
            if not enabled:
                request.reasoning_effort = "none"
                return

        config = self.template_manager.reasoning_config
        is_mistral = (config is not None and config.special_case == "mistral") or (
            config is None and self._reasoning_default_mode() == "mistral"
        )
        if is_mistral:
            request.reasoning_effort = "medium" if enabled else "none"
            return

        is_always_on = (config is not None and config.special_case == "always") or (
            config is None and self._reasoning_default_mode() == "always"
        )
        if is_always_on:
            if not enabled:
                raise ValueError(
                    f"Reasoning parser '{self.reasoning_parser}' is always-on "
                    f"and cannot be disabled via Anthropic thinking"
                )
            return

        toggle_param = self._get_reasoning_toggle_param()
        # The read side (``_get_reasoning_from_request``) returns False
        # whenever ``config.toggle_param is None`` OR
        # ``config.default_enabled is None``. The write side must mirror
        # both conditions: if ``default_enabled`` is unset we cannot
        # actually honor an ``enabled=True`` request even when the toggle
        # name itself is resolvable, so writing the kwarg would set up the
        # template to emit reasoning tokens while the parser ignores them
        # (literal ``<think>`` markers leak into the assistant text).
        config = self.template_manager.reasoning_config
        read_side_supported = toggle_param is not None and (
            config is None or config.default_enabled is not None
        )

View on GitHub (pinned to 0132848349)