Files
Gérald LONLASandClaude Opus 4.8 873343667e feat(connect): tri-state thinking control, native model dropdown, UI rework (#19)
Replace the binary "Enable Reasoning" toggle with a tri-state
Thinking / Reasoning control (auto / on / off) that drives LM Studio's
template-level reasoning switch via chat_template_kwargs.enable_thinking
(nested — a top-level field never reaches the Jinja template):

- off  -> suppress: enable_thinking:false + a closed <think></think>
          assistant prefill, so a reasoning model answers directly instead
          of burning its whole budget on a hidden think trace.
- on   -> force: enable_thinking:true.
- auto -> force for families that stay silent by default (Gemma), otherwise
          send nothing (Qwen3-style templates already think by default).

Applied to both the responses and chat.completions paths in the text and
image nodes, so reasoning is controlled consistently regardless of which
endpoint the server supports.

Fix the duplicate "Model" field: the web extension used to inject a second
combo widget and hide the backend string widget (unreliably), leaving two
model fields on screen. The model input is now a native io.Combo populated
in place by Refresh Models; `model` is listed in validate_inputs so ComfyUI
skips its "value not in list" check (the real options are only known at
runtime).

Rework the Connect node UI: essentials first (Server URL -> Model ->
Thinking), secondary settings moved to advanced, buttons show progress
labels and toast feedback instead of a blocking alert, and the status line
reports the resolved thinking mode.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 01:05:26 +08:00

73 lines
2.3 KiB
Python

from __future__ import annotations
import pytest
from helpers.imports import import_repo_module
def _client():
return import_repo_module("client", force_reload=True)
@pytest.mark.parametrize(
"model, expected",
[
("google/gemma-3-12b", True),
("Gemma2-9B", True),
("gemma", True),
("qwen3-8b", False),
("llama-3.1-8b", False),
("", False),
(None, False),
],
)
def test_model_thinks_off_by_default(model, expected) -> None:
client = _client()
assert client.model_thinks_off_by_default(model) is expected
@pytest.mark.parametrize(
"thinking, model, expected",
[
# Explicit off/on always win regardless of family.
("off", "qwen3-8b", "suppress"),
("off", "gemma-3", "suppress"),
("on", "gemma-3", "force"),
("on", "qwen3-8b", "force"),
# auto defers for thinking-by-default families, forces Gemma.
("auto", "qwen3-8b", "default"),
("auto", "google/gemma-3-12b", "force"),
# Normalization: case/whitespace and unknown/blank fall back to auto.
(" OFF ", "qwen", "suppress"),
("", "qwen", "default"),
(None, "gemma-3", "force"),
],
)
def test_resolve_thinking_mode(thinking, model, expected) -> None:
client = _client()
assert client.resolve_thinking_mode(thinking, model) == expected
def test_thinking_chat_template_kwargs() -> None:
client = _client()
assert client.thinking_chat_template_kwargs(client.THINK_MODE_SUPPRESS) == {
"enable_thinking": False
}
assert client.thinking_chat_template_kwargs(client.THINK_MODE_FORCE) == {
"enable_thinking": True
}
assert client.thinking_chat_template_kwargs(client.THINK_MODE_DEFAULT) is None
def test_apply_thinking_prefill_only_on_suppress() -> None:
client = _client()
messages = [{"role": "user", "content": "hi"}]
suppressed = client.apply_thinking_prefill(messages, client.THINK_MODE_SUPPRESS)
assert suppressed[-1] == {"role": "assistant", "content": "<think></think>"}
# Original list is not mutated.
assert messages == [{"role": "user", "content": "hi"}]
for mode in (client.THINK_MODE_FORCE, client.THINK_MODE_DEFAULT):
assert client.apply_thinking_prefill(messages, mode) == messages