fix(metadata): record the actual checkpoint on Set/Get and stale-cache workflows

trace_model_path dead-ended on KJNodes GetNode virtual links and fell back
to the first entry in MODELS, which could be a stale checkpoint name
resurrected from the process-lifetime node_cache — images ended up with a
wrong or missing Hashes.model.

- SetNodeExtractor now records MODEL passthrough as a model_variable entry
  (variable name -> source node id), cached per node like other metadata
- trace_model_path resolves GetNode variable references through those
  entries instead of giving up (last SetNode wins, per KJNodes semantics)
- _fill_missing_metadata validates cached checkpoint names against the
  node's current widget values and rebuilds stale entries from the current
  inputs instead of resurrecting the previous model
This commit is contained in:
Will Miao
2026-10-09 20:44:39 +08:00
parent 1a6b0f78a3
commit 406096c0e1
4 changed files with 370 additions and 7 deletions
+64 -3
View File
@@ -7,7 +7,7 @@ from .constants import IMAGES
standalone_mode = os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1" or os.environ.get("HF_HUB_DISABLE_TELEMETRY", "0") == "0"
from .constants import MODELS, PROMPTS, SAMPLING, LORAS, SIZE, IS_SAMPLER, OVERWRITE
from .node_extractors import NODE_EXTRACTORS
from .node_extractors import NODE_EXTRACTORS, _get_variable_name
logger = logging.getLogger(__name__)
@@ -356,8 +356,18 @@ class MetadataProcessor:
# Handle pipe nodes like FromBasicPipe by following the pipeline
next_input_name = "basic_pipe"
else:
# Dead end - no model input to follow
return None
# No direct model input to follow. GetNode (KJNodes) links
# are virtual and absent from the API prompt — resolve the
# variable reference back to the SetNode's MODEL source
# before giving up.
resolved_id = MetadataProcessor._resolve_model_variable_source(
metadata, prompt, current_node_id
)
if resolved_id is None:
return None
current_node_id = resolved_id
depth += 1
continue
# Get connected node
input_val = inputs[next_input_name]
@@ -370,6 +380,57 @@ class MetadataProcessor:
return None
@staticmethod
def _resolve_model_variable_source(metadata, prompt, node_id):
"""Resolve a GetNode-style virtual reference to its MODEL source node.
SetNodeExtractor records ``model_variable`` entries (variable name →
source node id) when a SetNode carrying a MODEL link executes, and
GetNodeExtractor records the variable name each GetNode reads. When
both are available, the virtual Set/Get link can be followed just like
a real connection.
"""
if not prompt or not getattr(prompt, "original_prompt", None):
return None
if node_id not in prompt.original_prompt:
return None
# Variable name read by this node: prefer the runtime record,
# fall back to the prompt inputs.
variable_name = None
prompt_info = metadata.get(PROMPTS, {}).get(node_id)
if isinstance(prompt_info, dict):
variable_name = prompt_info.get("variable_name")
if not variable_name:
node_inputs = prompt.original_prompt[node_id].get("inputs", {})
variable_name = _get_variable_name(node_inputs)
if not variable_name:
return None
candidates = [
info
for info in metadata.get(MODELS, {}).values()
if isinstance(info, dict)
and info.get("type") == "model_variable"
and info.get("variable_name") == variable_name
and info.get("source_node_id")
]
if not candidates:
return None
# KJNodes semantics: when several SetNodes share a variable name,
# the last one to execute wins.
execution_order = metadata.get("execution_order") or []
def _order(info):
try:
return execution_order.index(info.get("node_id"))
except ValueError:
return -1
best = max(candidates, key=_order)
return best.get("source_node_id")
@staticmethod
def find_primary_checkpoint(metadata, downstream_id=None, primary_sampler_id=None):
"""