Compare commits

..
Author SHA1 Message Date
Gerald LonlasandClaude Opus 4.7 a6d471fc9b fix: accept extra kwargs in LMStudioConnect.validate_inputs
ComfyUI can pass any of a node's schema inputs to validate_inputs by
name, depending on the schema version. The current signature only
accepts server_url, timeout_seconds, and max_tokens, so extra inputs
(api_token, model, reasoning_enabled, ...) would raise TypeError before
validation ever runs.

Add **_: Any to absorb any additional inputs ComfyUI provides while
keeping the existing validation logic intact.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:44:57 +08:00
6 changed files with 57 additions and 118 deletions
+9 -1
View File
@@ -1,5 +1,7 @@
from __future__ import annotations
from typing import Any
from comfy_api.latest import io, ui
from .client import (
@@ -115,7 +117,13 @@ class LMStudioConnect(io.ComfyNode):
)
@classmethod
def validate_inputs(cls, server_url: str, timeout_seconds: int, max_tokens: int) -> bool | str:
def validate_inputs(
cls,
server_url: str,
timeout_seconds: int,
max_tokens: int,
**_: Any,
) -> bool | str:
try:
normalize_server_url(server_url)
except ValueError as exc:
+15 -21
View File
@@ -164,27 +164,21 @@ class LMStudioImageToText(io.ComfyNode):
first_image = image[0] if is_batch else image
image_data_url = comfy_image_to_base64_png_url(first_image)
try:
completion = client.chat.completions.create(
model=connection.model,
messages=build_chat_messages_with_image(
system_prompt=system_prompt,
user_prompt=user_prompt,
image_data_url=image_data_url,
),
seed=resolved_seed,
temperature=connection.temperature,
max_tokens=connection.max_tokens,
n=1,
)
text = extract_chat_completion_text(completion).strip()
if not text:
raise ValueError("chat.completions fallback returned no text output")
except Exception as chat_error:
raise RuntimeError(
f"Both endpoints failed. responses error: {fallback_reason}. "
f"chat.completions error: {chat_error}"
) from chat_error
completion = client.chat.completions.create(
model=connection.model,
messages=build_chat_messages_with_image(
system_prompt=system_prompt,
user_prompt=user_prompt,
image_data_url=image_data_url,
),
seed=resolved_seed,
temperature=connection.temperature,
max_tokens=connection.max_tokens,
n=1,
)
text = extract_chat_completion_text(completion).strip()
if not text:
raise ValueError("chat.completions fallback returned no text output")
if batch_size > 1:
fallback_reason += (
+21
View File
@@ -49,6 +49,27 @@ def test_validate_inputs() -> None:
assert cls.validate_inputs("http://127.0.0.1:1234", 10, 0) == "max_tokens must be >= 1"
def test_validate_inputs_accepts_extra_kwargs() -> None:
connect_node = _connect_module()
cls = connect_node.LMStudioConnect
# ComfyUI may pass any of the schema inputs by name; the method must tolerate them.
assert (
cls.validate_inputs(
"http://127.0.0.1:1234",
10,
128,
api_token="-",
model="placeholder",
reasoning_enabled=False,
use_tooling_mcp=False,
temperature=0.7,
test_connectivity=True,
)
is True
)
def test_execute_with_placeholder_model_uses_discovered_default(
monkeypatch: pytest.MonkeyPatch,
) -> None:
-38
View File
@@ -158,41 +158,3 @@ def test_execute_chat_fallback_reports_dropped_frames(
assert "via chat.completions" in output.ui.text
assert "responses unavailable" in output.ui.text
assert "additional frame(s) were dropped" in output.ui.text
def test_execute_chains_both_errors_when_chat_completions_also_raises(
monkeypatch: pytest.MonkeyPatch,
) -> None:
image_node = _image_module()
def raise_responses(**kwargs):
raise RuntimeError("responses boom")
def raise_chat(**kwargs):
raise RuntimeError("chat boom")
fake_client = SimpleNamespace(
responses=SimpleNamespace(create=raise_responses),
chat=SimpleNamespace(completions=SimpleNamespace(create=raise_chat)),
)
monkeypatch.setattr(image_node, "resolve_request_seed", lambda _: 41)
monkeypatch.setattr(image_node, "create_openai_client", lambda **_: fake_client)
monkeypatch.setattr(
image_node, "comfy_image_to_base64_png_url", lambda _img: "data:image/png;base64,xxx"
)
with pytest.raises(RuntimeError) as exc_info:
image_node.LMStudioImageToText.execute(
connection=_connection_payload(),
image=_single_image(),
system_prompt="sys",
user_prompt="describe",
seed=0,
)
message = str(exc_info.value)
assert "Both endpoints failed" in message
assert "responses boom" in message
assert "chat boom" in message
assert isinstance(exc_info.value.__cause__, RuntimeError)
assert str(exc_info.value.__cause__) == "chat boom"
+1 -41
View File
@@ -161,50 +161,10 @@ def test_execute_raises_if_both_endpoints_return_no_text(
monkeypatch.setattr(text_gen_node, "create_openai_client", lambda **_: fake_client)
monkeypatch.setattr(text_gen_node, "resolve_request_seed", lambda _: 55)
with pytest.raises(RuntimeError) as exc_info:
with pytest.raises(ValueError, match="chat\\.completions fallback returned no text output"):
text_gen_node.LMStudioTextGen.execute(
connection=_connection_payload(),
system_prompt="sys",
user_prompt="hello",
seed=1,
)
message = str(exc_info.value)
assert "Both endpoints failed" in message
assert "responses endpoint returned no text output" in message
assert "chat.completions fallback returned no text output" in message
def test_execute_chains_both_errors_when_chat_completions_also_raises(
monkeypatch: pytest.MonkeyPatch,
) -> None:
text_gen_node = _text_module()
def raise_responses(**kwargs):
raise RuntimeError("responses boom")
def raise_chat(**kwargs):
raise RuntimeError("chat boom")
fake_client = SimpleNamespace(
responses=SimpleNamespace(create=raise_responses),
chat=SimpleNamespace(completions=SimpleNamespace(create=raise_chat)),
)
monkeypatch.setattr(text_gen_node, "create_openai_client", lambda **_: fake_client)
monkeypatch.setattr(text_gen_node, "resolve_request_seed", lambda _: 55)
with pytest.raises(RuntimeError) as exc_info:
text_gen_node.LMStudioTextGen.execute(
connection=_connection_payload(),
system_prompt="sys",
user_prompt="hello",
seed=1,
)
message = str(exc_info.value)
assert "Both endpoints failed" in message
assert "responses boom" in message
assert "chat boom" in message
assert isinstance(exc_info.value.__cause__, RuntimeError)
assert str(exc_info.value.__cause__) == "chat boom"
+11 -17
View File
@@ -136,23 +136,17 @@ class LMStudioTextGen(io.ComfyNode):
via_endpoint = "chat.completions"
fallback_reason = str(responses_error)
try:
completion = client.chat.completions.create(
model=connection.model,
messages=build_chat_messages(system_prompt, user_prompt),
seed=resolved_seed,
temperature=connection.temperature,
max_tokens=connection.max_tokens,
n=1,
)
text = extract_chat_completion_text(completion).strip()
if not text:
raise ValueError("chat.completions fallback returned no text output")
except Exception as chat_error:
raise RuntimeError(
f"Both endpoints failed. responses error: {fallback_reason}. "
f"chat.completions error: {chat_error}"
) from chat_error
completion = client.chat.completions.create(
model=connection.model,
messages=build_chat_messages(system_prompt, user_prompt),
seed=resolved_seed,
temperature=connection.temperature,
max_tokens=connection.max_tokens,
n=1,
)
text = extract_chat_completion_text(completion).strip()
if not text:
raise ValueError("chat.completions fallback returned no text output")
text = strip_think_content(text)