Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6d471fc9b |
+9
-1
@@ -1,5 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from comfy_api.latest import io, ui
|
from comfy_api.latest import io, ui
|
||||||
|
|
||||||
from .client import (
|
from .client import (
|
||||||
@@ -115,7 +117,13 @@ class LMStudioConnect(io.ComfyNode):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@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:
|
try:
|
||||||
normalize_server_url(server_url)
|
normalize_server_url(server_url)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
|
|||||||
+15
-21
@@ -164,27 +164,21 @@ class LMStudioImageToText(io.ComfyNode):
|
|||||||
first_image = image[0] if is_batch else image
|
first_image = image[0] if is_batch else image
|
||||||
image_data_url = comfy_image_to_base64_png_url(first_image)
|
image_data_url = comfy_image_to_base64_png_url(first_image)
|
||||||
|
|
||||||
try:
|
completion = client.chat.completions.create(
|
||||||
completion = client.chat.completions.create(
|
model=connection.model,
|
||||||
model=connection.model,
|
messages=build_chat_messages_with_image(
|
||||||
messages=build_chat_messages_with_image(
|
system_prompt=system_prompt,
|
||||||
system_prompt=system_prompt,
|
user_prompt=user_prompt,
|
||||||
user_prompt=user_prompt,
|
image_data_url=image_data_url,
|
||||||
image_data_url=image_data_url,
|
),
|
||||||
),
|
seed=resolved_seed,
|
||||||
seed=resolved_seed,
|
temperature=connection.temperature,
|
||||||
temperature=connection.temperature,
|
max_tokens=connection.max_tokens,
|
||||||
max_tokens=connection.max_tokens,
|
n=1,
|
||||||
n=1,
|
)
|
||||||
)
|
text = extract_chat_completion_text(completion).strip()
|
||||||
text = extract_chat_completion_text(completion).strip()
|
if not text:
|
||||||
if not text:
|
raise ValueError("chat.completions fallback returned no text output")
|
||||||
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
|
|
||||||
|
|
||||||
if batch_size > 1:
|
if batch_size > 1:
|
||||||
fallback_reason += (
|
fallback_reason += (
|
||||||
|
|||||||
@@ -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"
|
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(
|
def test_execute_with_placeholder_model_uses_discovered_default(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -158,41 +158,3 @@ def test_execute_chat_fallback_reports_dropped_frames(
|
|||||||
assert "via chat.completions" in output.ui.text
|
assert "via chat.completions" in output.ui.text
|
||||||
assert "responses unavailable" in output.ui.text
|
assert "responses unavailable" in output.ui.text
|
||||||
assert "additional frame(s) were dropped" 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"
|
|
||||||
|
|||||||
@@ -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, "create_openai_client", lambda **_: fake_client)
|
||||||
monkeypatch.setattr(text_gen_node, "resolve_request_seed", lambda _: 55)
|
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(
|
text_gen_node.LMStudioTextGen.execute(
|
||||||
connection=_connection_payload(),
|
connection=_connection_payload(),
|
||||||
system_prompt="sys",
|
system_prompt="sys",
|
||||||
user_prompt="hello",
|
user_prompt="hello",
|
||||||
seed=1,
|
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
@@ -136,23 +136,17 @@ class LMStudioTextGen(io.ComfyNode):
|
|||||||
via_endpoint = "chat.completions"
|
via_endpoint = "chat.completions"
|
||||||
fallback_reason = str(responses_error)
|
fallback_reason = str(responses_error)
|
||||||
|
|
||||||
try:
|
completion = client.chat.completions.create(
|
||||||
completion = client.chat.completions.create(
|
model=connection.model,
|
||||||
model=connection.model,
|
messages=build_chat_messages(system_prompt, user_prompt),
|
||||||
messages=build_chat_messages(system_prompt, user_prompt),
|
seed=resolved_seed,
|
||||||
seed=resolved_seed,
|
temperature=connection.temperature,
|
||||||
temperature=connection.temperature,
|
max_tokens=connection.max_tokens,
|
||||||
max_tokens=connection.max_tokens,
|
n=1,
|
||||||
n=1,
|
)
|
||||||
)
|
text = extract_chat_completion_text(completion).strip()
|
||||||
text = extract_chat_completion_text(completion).strip()
|
if not text:
|
||||||
if not text:
|
raise ValueError("chat.completions fallback returned no text output")
|
||||||
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
|
|
||||||
|
|
||||||
text = strip_think_content(text)
|
text = strip_think_content(text)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user