Default coding-agent servers to reasoning off (#7521)

* Default coding agent servers to reasoning off

* Fix reasoning startup compatibility and attach warning
This commit is contained in:
Lee Jackson 2026-07-28 11:13:54 +01:00 committed by GitHub
commit 31699f9c04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 69 additions and 2 deletions

View file

@ -225,6 +225,16 @@ _TOOL_CALL_NUDGING_OPTION = typer.Option(
help = "Retry once with a nudge when a non-streaming passthrough tool call can't be healed. "
"On by default; when the flag is omitted an inherited UNSLOTH_TOOL_CALL_NUDGE is kept.",
)
_REASONING_OPTION = typer.Option(
None,
"--reasoning",
rich_help_panel = _PANEL_SERVER,
help = (
"llama-server reasoning mode for an auto-started coding-agent server. "
"Defaults to off so tool calls stay in the structured tool channel; use "
"'auto' or 'on' to opt back into model reasoning."
),
)
# Sampling overrides pin a value on the auto-started server (winning over the client and the
# per-model recommendation). Default unset -> the model's recommended sampling is used.
_TEMPERATURE_OPTION = typer.Option(
@ -479,6 +489,7 @@ class ServerOptions(NamedTuple):
enable_tools: bool = False
tool_call_healing: Optional[bool] = None
tool_call_nudging: Optional[bool] = None
reasoning: Optional[Literal["on", "off", "auto"]] = None
temperature: Optional[float] = None
top_p: Optional[float] = None
top_k: Optional[int] = None
@ -1020,6 +1031,10 @@ def _start_studio_server(
# Own session/process group so a mid-session Ctrl+C (cancel a turn) doesn't reach the
# server. It survives a successful agent session; torn down on startup/launch failure.
child_env = os.environ.copy()
# Current llama-server versions read this documented env equivalent of --reasoning.
# Older managed versions ignore an unknown env variable instead of failing startup on
# an unknown passthrough CLI flag. An omitted start option still defaults to off.
child_env["LLAMA_ARG_REASONING"] = server.reasoning or "off"
# Pass the marker via env so an older launcher ignores it instead of treating an
# unknown CLI flag as a llama-server arg; new launchers preserve it across re-exec.
child_env[_START_API_KEY_MARKER_ENV] = "1"
@ -1159,6 +1174,14 @@ def _require_studio(
"and re-run to apply them.",
err = True,
)
if server_options.reasoning is not None:
typer.echo(
f"Warning: an Unsloth server is already running at {base}; "
f"--reasoning {server_options.reasoning} applies only when this command starts "
"the server, so the running server keeps its current reasoning mode. Stop it "
"with `unsloth studio stop` and re-run to apply the override.",
err = True,
)
return base, None
expected = os.environ.get("UNSLOTH_STUDIO_URL", "http://127.0.0.1:8888").rstrip("/")
# Auto-start a local server only for an interactive launch with a model to serve, and
@ -3048,6 +3071,7 @@ def claude(
enable_tools: bool = _ENABLE_TOOLS_OPTION,
tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION,
tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION,
reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION,
temperature: Optional[float] = _TEMPERATURE_OPTION,
top_p: Optional[float] = _TOP_P_OPTION,
top_k: Optional[int] = _TOP_K_OPTION,
@ -3072,6 +3096,7 @@ def claude(
enable_tools = enable_tools,
tool_call_healing = tool_call_healing,
tool_call_nudging = tool_call_nudging,
reasoning = reasoning,
temperature = temperature,
top_p = top_p,
top_k = top_k,
@ -3166,6 +3191,7 @@ def codex(
enable_tools: bool = _ENABLE_TOOLS_OPTION,
tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION,
tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION,
reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION,
temperature: Optional[float] = _TEMPERATURE_OPTION,
top_p: Optional[float] = _TOP_P_OPTION,
top_k: Optional[int] = _TOP_K_OPTION,
@ -3190,6 +3216,7 @@ def codex(
enable_tools = enable_tools,
tool_call_healing = tool_call_healing,
tool_call_nudging = tool_call_nudging,
reasoning = reasoning,
temperature = temperature,
top_p = top_p,
top_k = top_k,
@ -3265,6 +3292,7 @@ def openclaw(
enable_tools: bool = _ENABLE_TOOLS_OPTION,
tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION,
tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION,
reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION,
temperature: Optional[float] = _TEMPERATURE_OPTION,
top_p: Optional[float] = _TOP_P_OPTION,
top_k: Optional[int] = _TOP_K_OPTION,
@ -3289,6 +3317,7 @@ def openclaw(
enable_tools = enable_tools,
tool_call_healing = tool_call_healing,
tool_call_nudging = tool_call_nudging,
reasoning = reasoning,
temperature = temperature,
top_p = top_p,
top_k = top_k,
@ -3346,6 +3375,7 @@ def opencode(
enable_tools: bool = _ENABLE_TOOLS_OPTION,
tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION,
tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION,
reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION,
temperature: Optional[float] = _TEMPERATURE_OPTION,
top_p: Optional[float] = _TOP_P_OPTION,
top_k: Optional[int] = _TOP_K_OPTION,
@ -3370,6 +3400,7 @@ def opencode(
enable_tools = enable_tools,
tool_call_healing = tool_call_healing,
tool_call_nudging = tool_call_nudging,
reasoning = reasoning,
temperature = temperature,
top_p = top_p,
top_k = top_k,
@ -3507,6 +3538,7 @@ def hermes(
enable_tools: bool = _ENABLE_TOOLS_OPTION,
tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION,
tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION,
reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION,
temperature: Optional[float] = _TEMPERATURE_OPTION,
top_p: Optional[float] = _TOP_P_OPTION,
top_k: Optional[int] = _TOP_K_OPTION,
@ -3533,6 +3565,7 @@ def hermes(
enable_tools = enable_tools,
tool_call_healing = tool_call_healing,
tool_call_nudging = tool_call_nudging,
reasoning = reasoning,
temperature = temperature,
top_p = top_p,
top_k = top_k,
@ -3564,6 +3597,7 @@ def pi(
enable_tools: bool = _ENABLE_TOOLS_OPTION,
tool_call_healing: Optional[bool] = _TOOL_CALL_HEALING_OPTION,
tool_call_nudging: Optional[bool] = _TOOL_CALL_NUDGING_OPTION,
reasoning: Optional[Literal["on", "off", "auto"]] = _REASONING_OPTION,
temperature: Optional[float] = _TEMPERATURE_OPTION,
top_p: Optional[float] = _TOP_P_OPTION,
top_k: Optional[int] = _TOP_K_OPTION,
@ -3588,6 +3622,7 @@ def pi(
enable_tools = enable_tools,
tool_call_healing = tool_call_healing,
tool_call_nudging = tool_call_nudging,
reasoning = reasoning,
temperature = temperature,
top_p = top_p,
top_k = top_k,

View file

@ -1993,6 +1993,8 @@ def test_start_studio_server_forwards_tool_flags_via_command_and_env(monkeypatch
start._start_studio_server("http://127.0.0.1:8888", "unsloth/M-GGUF", start.LoadOptions())
cmd, env = captured["command"], captured["kwargs"]["env"]
assert "--disable-tools" in cmd and "--enable-tools" not in cmd
assert "--reasoning" not in cmd
assert env["LLAMA_ARG_REASONING"] == "off"
assert "--gpu-memory-mode" not in cmd
assert env["UNSLOTH_DISABLE_TOOL_CALL_HEALING"] == "0"
assert env["UNSLOTH_TOOL_CALL_NUDGE"] == "1"
@ -2002,10 +2004,17 @@ def test_start_studio_server_forwards_tool_flags_via_command_and_env(monkeypatch
"http://127.0.0.1:8888",
"unsloth/M-GGUF",
start.LoadOptions(),
start.ServerOptions(enable_tools = True, tool_call_healing = False, tool_call_nudging = False),
start.ServerOptions(
enable_tools = True,
tool_call_healing = False,
tool_call_nudging = False,
reasoning = "auto",
),
)
cmd, env = captured["command"], captured["kwargs"]["env"]
assert "--enable-tools" in cmd and "--disable-tools" not in cmd
assert "--reasoning" not in cmd
assert env["LLAMA_ARG_REASONING"] == "auto"
assert env["UNSLOTH_DISABLE_TOOL_CALL_HEALING"] == "1"
assert env["UNSLOTH_TOOL_CALL_NUDGE"] == "0"
@ -2118,7 +2127,25 @@ def test_require_studio_no_sampling_warning_without_pins(monkeypatch, capsys):
server_options = start.ServerOptions(enable_tools = True),
)
assert base == BASE and server is None
assert "sampling" not in capsys.readouterr().err.lower()
assert capsys.readouterr().err == ""
@pytest.mark.parametrize("reasoning", ["on", "off", "auto"])
def test_require_studio_warns_on_explicit_reasoning_when_reusing_server(
monkeypatch, capsys, reasoning
):
monkeypatch.setattr(start, "find_studio_server", lambda: BASE)
base, server = start._require_studio(
"unsloth/M-GGUF",
start.LoadOptions(),
serve = True,
server_options = start.ServerOptions(reasoning = reasoning),
)
assert base == BASE and server is None
err = capsys.readouterr().err
assert "already running" in err
assert f"--reasoning {reasoning}" in err
assert "unsloth studio stop" in err
def test_start_claude_parses_sampling_flags(fake_studio, monkeypatch):
@ -2153,11 +2180,14 @@ def test_start_claude_parses_sampling_flags(fake_studio, monkeypatch):
"0.3",
"--top-k",
"40",
"--reasoning",
"on",
],
)
assert result.exit_code == 0, result.output
so = captured["server_options"]
assert so.temperature == 0.3 and so.top_k == 40 and so.top_p is None
assert so.reasoning == "on"
def test_connect_model_bare_id_matches_loaded_without_reload(fake_studio):
@ -2681,6 +2711,8 @@ def test_start_studio_server_builds_command_and_waits(monkeypatch, capsys):
cmd = captured["command"]
assert cmd[1] == "run"
assert "--disable-tools" in cmd and "--no-cloudflare" in cmd
assert "--reasoning" not in cmd
assert captured["kwargs"]["env"]["LLAMA_ARG_REASONING"] == "off"
assert cmd[cmd.index("--model") + 1] == "unsloth/Qwen3-1.7B-GGUF:UD-Q4_K_XL"
assert cmd[cmd.index("--gguf-variant") + 1] == "UD-Q4_K_XL"
assert cmd[cmd.index("--context-length") + 1] == "8192"