mirror of
https://github.com/Comfy-Org/ComfyUI.git
synced 2026-08-24 10:03:55 +08:00
Fix Generate LTX2 Prompt returning an empty string
Non-thinking mode primes a "final" channel, so the Gemma 4 tokenizer decodes that channel's close marker to a bare </think> with no opening tag. Splitting on the last </think> then kept everything after it, which is nothing: the node output an empty string, blanking both Preview Any and the positive conditioning it feeds in the Text to Video (LTX-2.5) template. Fall back to the text before the unmatched close, and to the user prompt when nothing usable is left, which is what both system prompts already ask for.
This commit is contained in:
@@ -211,6 +211,24 @@ AESTHETIC QUALITY (in addition to the above, without breaking the objective capt
|
||||
"""
|
||||
|
||||
|
||||
LTX2_MARKERS = re.compile(r"</?think>|<\|channel>\w*\n?|<channel\|>|<\|turn>\w*\n?")
|
||||
|
||||
|
||||
def parse_ltx2_prompt(generated_text, prompt):
|
||||
"""Strip reasoning blocks and channel markers from the generated LTX2 prompt.
|
||||
|
||||
A close marker with no opening tag leaves nothing after the split, so fall back to the
|
||||
text before it and then to the user prompt. Both system prompts ask for the original
|
||||
prompt back when the model has nothing to give, and an empty string here silently
|
||||
generates the video with no conditioning at all.
|
||||
"""
|
||||
text = re.sub(r"<think>.*?</think>", "", generated_text, flags=re.DOTALL)
|
||||
if "</think>" in text: # unclosed/truncated reasoning: keep what follows the last close
|
||||
head, _, tail = text.rpartition("</think>")
|
||||
text = tail if LTX2_MARKERS.sub("", tail).strip() else head
|
||||
return LTX2_MARKERS.sub("", text).strip() or prompt
|
||||
|
||||
|
||||
class TextGenerateLTX2Prompt(TextGenerate):
|
||||
@classmethod
|
||||
def define_schema(cls):
|
||||
@@ -256,12 +274,7 @@ class TextGenerateLTX2Prompt(TextGenerate):
|
||||
|
||||
out = super().execute(clip, formatted_prompt, max_length, sampling_mode, image=image, thinking=thinking, use_default_template=use_default_template, video=video, audio=audio)
|
||||
|
||||
text = out.args[0]
|
||||
text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
|
||||
if "</think>" in text: # unclosed/truncated reasoning: keep what follows the last close
|
||||
text = text.rsplit("</think>", 1)[-1]
|
||||
text = re.sub(r"</?think>|<\|channel>\w*\n?|<channel\|>|<\|turn>\w*\n?", "", text).strip()
|
||||
return io.NodeOutput(text)
|
||||
return io.NodeOutput(parse_ltx2_prompt(out.args[0], prompt))
|
||||
|
||||
|
||||
class TextgenExtension(ComfyExtension):
|
||||
|
||||
42
tests-unit/comfy_extras_test/nodes_textgen_test.py
Normal file
42
tests-unit/comfy_extras_test/nodes_textgen_test.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from comfy_extras.nodes_textgen import parse_ltx2_prompt
|
||||
|
||||
|
||||
USER_PROMPT = "a fennec girl waving at the camera"
|
||||
|
||||
|
||||
class TestParseLTX2Prompt:
|
||||
@staticmethod
|
||||
def _parse(generated_text: str) -> str:
|
||||
return parse_ltx2_prompt(generated_text, USER_PROMPT)
|
||||
|
||||
def test_plain_text(self):
|
||||
assert self._parse("an enhanced prompt") == "an enhanced prompt"
|
||||
|
||||
def test_dangling_close_keeps_text_before_it(self):
|
||||
# Non-thinking mode primes a "final" channel, whose close decodes to a lone </think>.
|
||||
assert self._parse("an enhanced prompt</think>") == "an enhanced prompt"
|
||||
|
||||
def test_text_after_reasoning_block(self):
|
||||
assert self._parse("<think>reasoning</think>an enhanced prompt") == "an enhanced prompt"
|
||||
|
||||
def test_reasoning_only_falls_back_to_prompt(self):
|
||||
assert self._parse("<think>reasoning</think>") == USER_PROMPT
|
||||
|
||||
def test_empty_generation_falls_back_to_prompt(self):
|
||||
assert self._parse("") == USER_PROMPT
|
||||
|
||||
def test_multiline_reasoning_block(self):
|
||||
assert self._parse("<think>\nline one\nline two\n</think>\nan enhanced prompt") == "an enhanced prompt"
|
||||
|
||||
def test_reasoning_block_before_dangling_close(self):
|
||||
assert self._parse("<think>reasoning</think>an enhanced prompt</think>") == "an enhanced prompt"
|
||||
|
||||
def test_channel_markers_are_stripped(self):
|
||||
assert self._parse("<|channel>final\nan enhanced prompt<channel|>") == "an enhanced prompt"
|
||||
|
||||
def test_turn_markers_are_stripped(self):
|
||||
assert self._parse("<|turn>model\nan enhanced prompt") == "an enhanced prompt"
|
||||
|
||||
def test_never_returns_empty(self):
|
||||
for generated_text in ["an enhanced prompt</think>", "<think>x</think>", "<think>x</think>an enhanced prompt", "an enhanced prompt", "</think>", "<channel|>", " "]:
|
||||
assert self._parse(generated_text) != ""
|
||||
Reference in New Issue
Block a user