fix(metadata): exclude scalar fields from conditioning provenance inputs

This commit is contained in:
Will Miao
2026-08-12 19:18:46 +08:00
parent 3f8381ffee
commit 5bc6d8286c
2 changed files with 148 additions and 12 deletions
+20 -12
View File
@@ -82,11 +82,7 @@ class GenericNodeExtractor(NodeMetadataExtractor):
text = val.strip()
break
input_conditionings = [
value
for input_name, value in inputs.items()
if input_name.startswith("conditioning") and value is not None
]
input_conditionings = _collect_conditioning_inputs(inputs)
if text or input_conditionings:
prompt_metadata = _ensure_prompt_metadata(metadata, node_id)
if text:
@@ -434,6 +430,24 @@ def _first_output_tuple(outputs):
return None
def _collect_conditioning_inputs(inputs):
"""Collect conditioning object inputs (``conditioning*`` keys).
Primitive values (None, str, int, float, bool) are excluded so scalar
fields like ``conditioning_strength`` are not mistaken for conditioning
objects during provenance tracking.
"""
if not inputs:
return []
return [
value
for input_name, value in inputs.items()
if input_name.startswith("conditioning")
and value is not None
and not isinstance(value, (str, int, float, bool))
]
def _record_conditioning_source(
metadata, node_id, output_conditioning, input_conditionings
):
@@ -525,13 +539,7 @@ class ConditioningCombineExtractor(NodeMetadataExtractor):
if not inputs:
return
input_conditionings = []
for input_name in inputs:
if (
input_name.startswith("conditioning")
and inputs[input_name] is not None
):
input_conditionings.append(inputs[input_name])
input_conditionings = _collect_conditioning_inputs(inputs)
if input_conditionings:
prompt_metadata = _ensure_prompt_metadata(metadata, node_id)
@@ -637,6 +637,134 @@ def test_conditioning_provenance_recovers_transformed_switched_prompts(
assert params["negative_prompt"] == "expected negative"
def test_conditioning_provenance_identity_switch_between_encoders(
metadata_registry, monkeypatch
):
"""Lock identity-preserving switches placed directly between encoders.
A switch returns the selected input conditioning verbatim, so provenance
must be recovered through object identity without any transform metadata.
"""
prompt_graph = {
"encode_pos": {
"class_type": "CLIPTextEncode",
"inputs": {"text": "chosen positive", "clip": ["clip", 0]},
},
"encode_other_pos": {
"class_type": "CLIPTextEncode",
"inputs": {"text": "unchosen positive", "clip": ["clip", 0]},
},
"positive_switch": {
"class_type": "ComfySwitchNode",
"inputs": {
"switch": True,
"on_false": ["encode_other_pos", 0],
"on_true": ["encode_pos", 0],
},
},
"sampler": {
"class_type": "ClownsharKSampler_Beta",
"inputs": {
"seed": 123,
"steps": 8,
"cfg": 1.0,
"sampler_name": "linear/euler",
"scheduler": "beta57",
"denoise": 1.0,
"positive": ["positive_switch", 0],
"negative": ["encode_other_pos", 0],
"latent_image": {
"samples": types.SimpleNamespace(shape=(1, 4, 16, 16))
},
},
},
}
prompt = SimpleNamespace(original_prompt=prompt_graph)
chosen_conditioning = object()
unchosen_conditioning = object()
monkeypatch.setattr(metadata_processor, "standalone_mode", False)
metadata_registry.start_collection("prompt-identity-switch")
metadata_registry.set_current_prompt(prompt)
metadata_registry.record_node_execution(
"encode_pos", "CLIPTextEncode", {"text": "chosen positive"}, None
)
metadata_registry.update_node_execution(
"encode_pos", "CLIPTextEncode", [(chosen_conditioning,)]
)
metadata_registry.record_node_execution(
"encode_other_pos", "CLIPTextEncode", {"text": "unchosen positive"}, None
)
metadata_registry.update_node_execution(
"encode_other_pos", "CLIPTextEncode", [(unchosen_conditioning,)]
)
metadata_registry.record_node_execution(
"positive_switch",
"ComfySwitchNode",
{
"switch": True,
"on_false": unchosen_conditioning,
"on_true": chosen_conditioning,
},
None,
)
metadata_registry.update_node_execution(
"positive_switch", "ComfySwitchNode", [(chosen_conditioning,)]
)
metadata_registry.record_node_execution(
"sampler",
"ClownsharKSampler_Beta",
{
"seed": 123,
"steps": 8,
"cfg": 1.0,
"sampler_name": "linear/euler",
"scheduler": "beta57",
"denoise": 1.0,
"positive": chosen_conditioning,
"negative": unchosen_conditioning,
"latent_image": {
"samples": types.SimpleNamespace(shape=(1, 4, 16, 16))
},
},
None,
)
metadata = metadata_registry.get_metadata("prompt-identity-switch")
params = MetadataProcessor.extract_generation_params(metadata)
assert params["prompt"] == "chosen positive"
assert params["negative_prompt"] == "unchosen positive"
def test_conditioning_provenance_ignores_scalar_conditioning_fields(
metadata_registry, monkeypatch
):
"""Scalar fields like ``conditioning_strength`` must not be collected as
conditioning objects for unregistered transform nodes."""
monkeypatch.setattr(metadata_processor, "standalone_mode", False)
metadata_registry.start_collection("prompt-scalar-filter")
metadata_registry.set_current_prompt(SimpleNamespace(original_prompt={}))
input_conditioning = object()
metadata_registry.record_node_execution(
"strength_node",
"SomeStrengthTransform",
{"conditioning": input_conditioning, "conditioning_strength": 0.8},
None,
return_types=("CONDITIONING",),
)
metadata = metadata_registry.get_metadata("prompt-scalar-filter")
assert metadata[PROMPTS]["strength_node"]["orig_conditionings"] == [
input_conditioning
]
def test_conditioning_provenance_recovers_kj_set_get_prompts(
metadata_registry, monkeypatch
):