Files
glonlas-comfyUI-LMStudio-nodes/tests/test_text_gen_node.py
Gerald LonlasandClaude Opus 4.7 2301e15111 fix: include both endpoint errors when chat.completions fallback also fails
When the responses endpoint fails and the chat.completions fallback also
fails (raises or returns empty), the original responses error was captured
in fallback_reason but never surfaced. Wrap the fallback call in its own
try/except and re-raise a RuntimeError that chains both errors with
context, so users see why both endpoints failed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 00:46:20 +08:00

211 lines
6.8 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
import pytest
from helpers.imports import import_repo_module
def _text_module():
return import_repo_module("text_gen_node", force_reload=True)
def _connection_payload():
models = import_repo_module("models", force_reload=True)
return models.LMStudioConnectionPayload(
server_url="http://127.0.0.1:1234",
base_url="http://127.0.0.1:1234/v1",
api_key="token",
model="model-a",
reasoning_enabled=False,
max_tokens=111,
temperature=0.4,
timeout_seconds=30,
use_tooling_mcp=False,
)
def test_define_schema_and_validate_inputs() -> None:
text_gen_node = _text_module()
schema = text_gen_node.LMStudioTextGen.define_schema()
assert schema.node_id == "LMStudio_TextGen"
assert [entry.id for entry in schema.inputs] == [
"connection",
"system_prompt",
"user_prompt",
"seed",
]
assert text_gen_node.LMStudioTextGen.validate_inputs(None) is True
def test_responses_kwargs_include_optional_fields() -> None:
text_gen_node = _text_module()
connection = _connection_payload()
connection = connection.__class__(**{**connection.__dict__, "reasoning_enabled": True, "use_tooling_mcp": True})
kwargs = text_gen_node.LMStudioTextGen._responses_kwargs(
connection=connection,
system_prompt="sys",
user_prompt="user",
seed=12,
)
assert kwargs["model"] == "model-a"
assert kwargs["seed"] == 12
assert kwargs["instructions"] == "sys"
assert kwargs["reasoning"] == {"effort": "medium"}
assert kwargs["metadata"] == {"lmstudio_tooling_mcp_requested": "true"}
def test_responses_kwargs_omit_optional_fields_when_disabled() -> None:
text_gen_node = _text_module()
connection = _connection_payload()
kwargs = text_gen_node.LMStudioTextGen._responses_kwargs(
connection=connection,
system_prompt=" ",
user_prompt="user",
seed=10,
)
assert "instructions" not in kwargs
assert "reasoning" not in kwargs
assert "metadata" not in kwargs
def test_execute_rejects_empty_user_prompt() -> None:
text_gen_node = _text_module()
with pytest.raises(ValueError, match="user_prompt must not be empty"):
text_gen_node.LMStudioTextGen.execute(
connection=_connection_payload(),
system_prompt="sys",
user_prompt=" ",
seed=1,
)
def test_execute_uses_responses_endpoint_and_strips_think(
monkeypatch: pytest.MonkeyPatch,
) -> None:
text_gen_node = _text_module()
captured_kwargs: dict[str, object] = {}
def create_response(**kwargs):
captured_kwargs.update(kwargs)
return SimpleNamespace(output_text="Answer <think>internal</think> final")
fake_client = SimpleNamespace(
responses=SimpleNamespace(create=create_response),
chat=SimpleNamespace(
completions=SimpleNamespace(
create=lambda **kwargs: (_ for _ in ()).throw(AssertionError("fallback should not run"))
)
),
)
monkeypatch.setattr(text_gen_node, "resolve_request_seed", lambda _: 77)
monkeypatch.setattr(text_gen_node, "create_openai_client", lambda **_: fake_client)
output = text_gen_node.LMStudioTextGen.execute(
connection=_connection_payload(),
system_prompt="sys",
user_prompt="hello",
seed=-1,
)
assert output[0] == "Answer final"
assert "via responses" in output.ui.text
assert "(seed=77)" in output.ui.text
assert captured_kwargs["seed"] == 77
def test_execute_falls_back_to_chat_completions(monkeypatch: pytest.MonkeyPatch) -> None:
text_gen_node = _text_module()
def raise_responses(**kwargs):
raise RuntimeError("responses disabled")
completion = SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(content="Fallback answer"))])
fake_client = SimpleNamespace(
responses=SimpleNamespace(create=raise_responses),
chat=SimpleNamespace(completions=SimpleNamespace(create=lambda **kwargs: completion)),
)
monkeypatch.setattr(text_gen_node, "resolve_request_seed", lambda _: 99)
monkeypatch.setattr(text_gen_node, "create_openai_client", lambda **_: fake_client)
output = text_gen_node.LMStudioTextGen.execute(
connection=_connection_payload(),
system_prompt="sys",
user_prompt="hello",
seed=0,
)
assert output[0] == "Fallback answer"
assert "via chat.completions" in output.ui.text
assert "responses disabled" in output.ui.text
def test_execute_raises_if_both_endpoints_return_no_text(
monkeypatch: pytest.MonkeyPatch,
) -> None:
text_gen_node = _text_module()
def empty_responses(**kwargs):
return SimpleNamespace(output_text="")
empty_completion = SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(content=""))])
fake_client = SimpleNamespace(
responses=SimpleNamespace(create=empty_responses),
chat=SimpleNamespace(completions=SimpleNamespace(create=lambda **kwargs: empty_completion)),
)
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 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"