Use ShowText cached output for LLM prompt chains

This commit is contained in:
CyberShadowX
2026-05-28 22:58:18 +08:00
parent 4d8cc18219
commit 5d359d3a54
4 changed files with 261 additions and 3 deletions
+14 -1
View File
@@ -187,7 +187,20 @@ class TextResolutionContext:
def is_llm_template_node(self, node: NodeRecord) -> bool:
lowered = node.class_type.lower()
markers = ("gemini", "chatgpt", "claude", "llm", "language_model")
markers = (
"gemini",
"openai",
"chatgpt",
"claude",
"llm",
"vlm",
"language_model",
"image caption",
"caption",
"prompt generator",
"promptgenerator",
"tagger",
)
return any(marker in lowered for marker in markers)
def record_unresolved(
+40 -1
View File
@@ -34,7 +34,7 @@ class StringFunctionAdapter:
class ShowTextAdapter:
def matches(self, node: NodeRecord) -> bool:
return node.class_type == "ShowText|pysssss"
return _is_show_text_node(node)
def resolve(
self,
@@ -50,6 +50,10 @@ class ShowTextAdapter:
if cached_text:
text, source = cached_text
confidence = 0.75 if source == "workflow widget cache fallback" else 1.0
context.trace.add(
"TEXT_TRACE",
f"{node.class_type} {node.node_id}: using cached ShowText text from {source}",
)
if source == "workflow widget cache fallback":
context.trace.add(
"TEXT_TRACE",
@@ -69,6 +73,23 @@ class ShowTextAdapter:
)
]
upstream_llm = self._upstream_llm_runtime_node(node, context)
if upstream_llm is not None:
context.trace.add(
"TEXT_TRACE",
(
f"{upstream_llm.class_type} {upstream_llm.node_id}: low confidence "
"LLM template input skipped, no cached generated output found"
),
)
context.record_unresolved(
node=node,
field="text",
role=role,
reason="ShowText cache missing and upstream is LLM runtime output not embedded",
)
return []
context.trace.add(
"TEXT_TRACE",
(
@@ -94,3 +115,21 @@ class ShowTextAdapter:
if workflow:
return workflow
return None
def _upstream_llm_runtime_node(
self,
node: NodeRecord,
context: TextResolutionContext,
) -> NodeRecord | None:
target = context.graph.link_target(node.inputs.get("text"))
if target is None:
return None
upstream = context.graph.get_node(target[0])
if upstream is not None and context.is_llm_template_node(upstream):
return upstream
return None
def _is_show_text_node(node: NodeRecord) -> bool:
lowered = node.class_type.lower().replace("_", "").replace(" ", "")
return lowered == "showtext" or lowered.startswith("showtext|")
+14 -1
View File
@@ -64,5 +64,18 @@ class GenericTextAdapter:
def context_marker_is_llm(lowered_class_type: str) -> bool:
markers = ("gemini", "chatgpt", "claude", "llm", "language_model")
markers = (
"gemini",
"openai",
"chatgpt",
"claude",
"llm",
"vlm",
"language_model",
"image caption",
"caption",
"prompt generator",
"promptgenerator",
"tagger",
)
return any(marker in lowered_class_type for marker in markers)
+193
View File
@@ -115,6 +115,93 @@ def _upscale_workflow(reader_image="003.png"):
}
def _llm_showtext_upscale_prompt(show_class="ShowText|pysssss", include_cache=True):
return {
"22": {
"class_type": "FaceDetailer",
"inputs": {"image": ["24", 0], "positive": ["26", 0], "negative": ["27", 0]},
},
"24": {
"class_type": "UltimateSDUpscale",
"inputs": {
"image": ["40", 0],
"positive": ["26", 0],
"negative": ["27", 0],
"seed": 222,
"steps": 9,
"cfg": 1.5,
"sampler_name": "Euler",
"scheduler": "normal",
},
},
"26": {"class_type": "CLIPTextEncode", "inputs": {"text": ["37", 0]}},
"27": {"class_type": "CLIPTextEncode", "inputs": {"text": "bad hands, low quality"}},
"37": {"class_type": show_class, "inputs": {"text": ["90", 0]}},
"40": {"class_type": "LoadImage", "inputs": {"image": "source.png"}},
"90": {
"class_type": "GeminiChatNode",
"inputs": {
"system_instruction": "system instruction should not enter prompt",
"prompt": "Gemini prompt template should not enter prompt",
"api_key": "secret-key",
"model": "gemini-test-model",
},
},
"91": {"class_type": "ShowText|pysssss", "inputs": {"text": ["90", 0]}},
"99": {"class_type": "PreviewImage", "inputs": {"images": ["22", 0]}},
}
def _llm_showtext_workflow(show_class="ShowText|pysssss", cached_text="cached gemini positive prompt"):
widgets_values = [cached_text] if cached_text else []
return {
"nodes": [
{
"id": 37,
"type": show_class,
"inputs": {"text": ["90", 0]},
"widgets_values": widgets_values,
},
{
"id": 90,
"type": "GeminiChatNode",
"inputs": {
"system_instruction": "system instruction should not enter prompt",
"prompt": "Gemini prompt template should not enter prompt",
"api_key": "secret-key",
"model": "gemini-test-model",
},
"widgets_values": [
"Gemini prompt template should not enter prompt",
"system instruction should not enter prompt",
"secret-key",
"gemini-test-model",
],
},
{
"id": 91,
"type": "ShowText|pysssss",
"inputs": {"text": ["90", 0]},
"widgets_values": ["wrong unused prompt"],
},
]
}
def _bundle_for_prompt(prompt, workflow=None, filename="sample.png", width=1200, height=1600):
from smart_metadata_reader.models import MetadataBundle
return MetadataBundle(
filename=filename,
width=width,
height=height,
prompt_raw=json.dumps(prompt),
workflow_raw=json.dumps(workflow) if workflow is not None else None,
parameters_raw=None,
source_format="ComfyUI prompt/workflow",
)
def test_read_metadata_extracts_png_text_chunks_and_dimensions(tmp_path):
from smart_metadata_reader.metadata_reader import read_metadata
@@ -609,3 +696,109 @@ def test_nested_smart_metadata_reader_respects_actual_connected_output_role(tmp_
assert result.positive == "original negative prompt"
assert "SmartMetadataReader output negative used in positive chain" in result.debug_trace
def test_showtext_cache_from_llm_chain_is_used_without_reading_llm_template():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
result = parse_metadata_bundle(
_bundle_for_prompt(
_llm_showtext_upscale_prompt(),
workflow=_llm_showtext_workflow(),
)
)
assert result.positive == "cached gemini positive prompt"
assert result.negative == "bad hands, low quality"
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert "secret-key" not in result.positive
assert result.status_message != "FAILED"
assert "using cached ShowText text" in result.debug_trace
assert "wrong unused prompt" not in result.positive
def test_showtext_cache_compatible_class_from_llm_chain_is_used():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
result = parse_metadata_bundle(
_bundle_for_prompt(
_llm_showtext_upscale_prompt(show_class="ShowText"),
workflow=_llm_showtext_workflow(show_class="ShowText"),
)
)
assert result.positive == "cached gemini positive prompt"
assert "Gemini prompt template" not in result.positive
def test_showtext_cache_is_single_segment_inside_string_function_chain():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
prompt = _llm_showtext_upscale_prompt()
prompt["26"]["inputs"]["text"] = ["39", 0]
prompt["39"] = {
"class_type": "StringFunction|pysssss",
"inputs": {
"action": "append",
"text_a": "manual prompt",
"text_b": ["37", 0],
"text_c": "preset prompt",
},
}
result = parse_metadata_bundle(
_bundle_for_prompt(prompt, workflow=_llm_showtext_workflow())
)
assert "manual prompt" in result.positive
assert "cached gemini positive prompt" in result.positive
assert "preset prompt" in result.positive
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert result.positive.index("manual prompt") < result.positive.index("cached gemini positive prompt")
assert result.positive.index("cached gemini positive prompt") < result.positive.index("preset prompt")
def test_conditioning_combine_showtext_cache_does_not_override_other_branches():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
prompt = _llm_showtext_upscale_prompt()
prompt["24"]["inputs"]["positive"] = ["41", 0]
prompt["41"] = {
"class_type": "ConditioningCombine",
"inputs": {"conditioning_1": ["42", 0], "conditioning_2": ["43", 0]},
}
prompt["42"] = {"class_type": "CLIPTextEncode", "inputs": {"text": "branch A manual prompt"}}
prompt["43"] = {"class_type": "CLIPTextEncode", "inputs": {"text": ["37", 0]}}
result = parse_metadata_bundle(
_bundle_for_prompt(prompt, workflow=_llm_showtext_workflow())
)
assert "branch A manual prompt" in result.positive
assert "cached gemini positive prompt" in result.positive
assert "Gemini prompt template" not in result.positive
def test_showtext_missing_cache_with_llm_upstream_returns_partial_unresolved():
from smart_metadata_reader.metadata_reader import parse_metadata_bundle
result = parse_metadata_bundle(
_bundle_for_prompt(
_llm_showtext_upscale_prompt(include_cache=False),
workflow=_llm_showtext_workflow(cached_text=""),
)
)
assert result.positive == ""
assert "Gemini prompt template" not in result.positive
assert "system instruction" not in result.positive
assert result.status_message == "PARTIAL"
assert result.confidence <= 0.6
assert result.partial_result["unresolved"][0]["class_type"] == "ShowText|pysssss"
assert (
result.partial_result["unresolved"][0]["reason"]
== "ShowText cache missing and upstream is LLM runtime output not embedded"
)
assert "Unresolved:" in result.setting