fix: surface reasoning effort in desktop GUI, delete dead variant knob (fixes #4695) (#4707)

Desktop: add reasoning_effort to DEFAULT_SETTINGS, the settings registry
(Models section) and _llm_overrides(), forwarded through start() only when a
real level is chosen -- "off" stays a no-op so a provider default is never
overridden. Reuses the exact path temperature/top_p already use, no new surface.

CLI: delete InteractiveConfig.variant, a documented "reasoning effort" knob with
zero consumers and no flag to set it.

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: MervinPraison <[email protected]>
This commit is contained in:
praisonai-triage-agent[bot]andMervinPraison authored and GitHub committed 2026-09-03 08:18:29 +01:00
1 parent 3c688fcb74
commit ba7019534e
6 files changed
+100 -4

No files matched your search

@@ -64,9 +64,6 @@ class InteractiveConfig:
# Sharing
share: bool = False
# Variant (reasoning effort)
variant: Optional[str] = None
@classmethod
def from_env(cls) -> "InteractiveConfig":
"""Create config from environment variables."""
@@ -97,7 +94,6 @@ class InteractiveConfig:
memory=getattr(args, "memory", False),
files=getattr(args, "file", []) or [],
share=getattr(args, "share", False),
variant=getattr(args, "variant", None),
autonomy=getattr(args, "autonomy", True),
no_context=getattr(args, "no_context", False),
)
+6
View File
@@ -713,6 +713,7 @@ DEFAULT_SETTINGS = {
"temperature": 0.7,
"max_tokens": 0,
"top_p": 1,
"reasoning_effort": "off",
"base_url": "",
"api_key": "",
"system_prompt": "",
@@ -1100,6 +1101,11 @@ def _llm_overrides(cfg: dict) -> dict:
out["max_tokens"] = int(cfg["max_tokens"])
if cfg.get("top_p") != DEFAULT_SETTINGS["top_p"]:
out["top_p"] = float(cfg["top_p"])
# "off" is the default no-op; anything else is a provider-portable effort
# level (minimal|low|medium|high) that Agent maps to each backend's native
# knob, exactly as the CLI's --thinking does.
if cfg.get("reasoning_effort") not in (None, "", DEFAULT_SETTINGS["reasoning_effort"]):
out["reasoning_effort"] = str(cfg["reasoning_effort"])
_apply_env(cfg)
return out
@@ -44,6 +44,7 @@ class StubAgent:
self.started = 0
self.chat_history = []
self.seen_history = None
self.seen_kwargs = None
def _append_to_chat_history(self, message):
self.chat_history.append(message)
@@ -51,6 +52,7 @@ class StubAgent:
def start(self, prompt, stream=True, **kwargs):
self.started += 1
self.seen_history = list(self.chat_history)
self.seen_kwargs = dict(kwargs)
# Tools "run" before the model's answer would arrive, exactly as the
# display callback records them during a real turn.
for tool in self.tools:
@@ -312,6 +314,71 @@ class HistoryAcrossSessions(unittest.TestCase):
f"a blank turn was replayed: {agent.seen_history}")
class ReasoningEffortReachesTheModel(unittest.TestCase):
"""The GUI setting must actually arrive at the turn.
reasoning_effort is a first-class Agent kwarg -- the engine forwards it
through start() alongside temperature/top_p, and only when the user chose a
real level. "off" is the default no-op and must not be sent, or it would
override a provider default the user never touched.
"""
@classmethod
def setUpClass(cls):
cls.server = ThreadingHTTPServer(("127.0.0.1", 0), server.Handler)
cls.port = cls.server.server_address[1]
cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True)
cls.thread.start()
@classmethod
def tearDownClass(cls):
cls.server.shutdown()
cls.server.server_close()
def setUp(self):
server._agents.clear()
server._tool_queue().clear()
self._orig = server.load_settings
def tearDown(self):
server.load_settings = self._orig
def _install(self):
agent = StubAgent(chunks=["ok"])
server._agents["t"] = agent
server._agents["t\x00notools"] = agent
return agent
def _chat(self):
body = json.dumps({"prompt": "hi", "chat_id": "t", "session": "t"}).encode()
req = urllib.request.Request(
f"http://127.0.0.1:{self.port}/chat", data=body, method="POST",
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=30) as response:
response.read()
def test_a_chosen_level_is_forwarded_to_start(self):
server.load_settings = lambda: dict(self._orig(), reasoning_effort="high")
agent = self._install()
self._chat()
self.assertEqual(agent.seen_kwargs.get("reasoning_effort"), "high",
"the GUI reasoning_effort never reached the turn")
def test_off_is_the_default_and_is_not_sent(self):
# Forwarding "off" unasked would override a provider default the user
# never touched, exactly as sending temperature=0.7 unasked would.
agent = self._install()
self._chat()
self.assertNotIn("reasoning_effort", agent.seen_kwargs,
"the no-op default was forwarded and overrode the provider")
def test_the_setting_round_trips_through_save(self):
# It is a real storage key, not a value dropped by load_settings.
server.save_settings({"reasoning_effort": "low"})
self.assertEqual(server.load_settings()["reasoning_effort"], "low")
server.save_settings({"reasoning_effort": "off"})
class FetchUrlDoesNotFollowRedirects(unittest.TestCase):
"""The approval card names one URL; the fetch must not reach another.
@@ -47,6 +47,15 @@ const SETTINGS = [
control: { kind: "slider", min: 0, max: 1, step: 0.01 },
default: 1, validate: clampNum(0, 1) },
{ key: "reasoning_effort", section: "model", label: "Reasoning effort",
description: "How hard the model thinks before answering. Off leaves the provider default. Maps to each backend's native knob.",
keywords: ["thinking", "reasoning", "effort", "budget", "o1", "o3"],
control: { kind: "select", options: [
{ value: "off", label: "Off" }, { value: "minimal", label: "Minimal" },
{ value: "low", label: "Low" }, { value: "medium", label: "Medium" },
{ value: "high", label: "High" }]},
default: "off" },
{ key: "base_url", section: "model", label: "Base URL override",
description: "Point at a proxy, Azure, or a local server. Blank uses the provider default.",
keywords: ["endpoint", "proxy", "azure", "ollama", "lm studio"],
+9
View File
@@ -676,6 +676,15 @@ const SETTINGS = [
control: { kind: "slider", min: 0, max: 1, step: 0.01 },
default: 1, validate: clampNum(0, 1) },
{ key: "reasoning_effort", section: "model", label: "Reasoning effort",
description: "How hard the model thinks before answering. Off leaves the provider default. Maps to each backend's native knob.",
keywords: ["thinking", "reasoning", "effort", "budget", "o1", "o3"],
control: { kind: "select", options: [
{ value: "off", label: "Off" }, { value: "minimal", label: "Minimal" },
{ value: "low", label: "Low" }, { value: "medium", label: "Medium" },
{ value: "high", label: "High" }]},
default: "off" },
{ key: "base_url", section: "model", label: "Base URL override",
description: "Point at a proxy, Azure, or a local server. Blank uses the provider default.",
keywords: ["endpoint", "proxy", "azure", "ollama", "lm studio"],
@@ -47,6 +47,15 @@ const SETTINGS = [
control: { kind: "slider", min: 0, max: 1, step: 0.01 },
default: 1, validate: clampNum(0, 1) },
{ key: "reasoning_effort", section: "model", label: "Reasoning effort",
description: "How hard the model thinks before answering. Off leaves the provider default. Maps to each backend's native knob.",
keywords: ["thinking", "reasoning", "effort", "budget", "o1", "o3"],
control: { kind: "select", options: [
{ value: "off", label: "Off" }, { value: "minimal", label: "Minimal" },
{ value: "low", label: "Low" }, { value: "medium", label: "Medium" },
{ value: "high", label: "High" }]},
default: "off" },
{ key: "base_url", section: "model", label: "Base URL override",
description: "Point at a proxy, Azure, or a local server. Blank uses the provider default.",
keywords: ["endpoint", "proxy", "azure", "ollama", "lm studio"],