Expose formatter route traces

This commit is contained in:
2026-06-27 15:20:04 +02:00
parent e7bc227c6f
commit 3d0a8cace8
8 changed files with 192 additions and 13 deletions
+30
View File
@@ -5,9 +5,11 @@ from typing import Any, Callable
try: try:
from . import formatter_input as input_policy from . import formatter_input as input_policy
from . import formatter_route_trace as trace_policy
from . import formatter_target as target_policy from . import formatter_target as target_policy
except ImportError: # pragma: no cover - plain-script smoke tests except ImportError: # pragma: no cover - plain-script smoke tests
import formatter_input as input_policy import formatter_input as input_policy
import formatter_route_trace as trace_policy
import formatter_target as target_policy import formatter_target as target_policy
@@ -35,10 +37,14 @@ class CaptionFormatRoute:
style_policy: str style_policy: str
include_trigger: bool include_trigger: bool
keep_style: bool keep_style: bool
route_trace_json: str = ""
def as_tuple(self) -> tuple[str, str]: def as_tuple(self) -> tuple[str, str]:
return self.caption, self.method return self.caption, self.method
def as_trace_tuple(self) -> tuple[str, str, str]:
return self.caption, self.method, self.route_trace_json
@dataclass(frozen=True) @dataclass(frozen=True)
class CaptionFormatDependencies: class CaptionFormatDependencies:
@@ -72,6 +78,17 @@ def naturalize_caption_result(
triggers=(request.trigger,), triggers=(request.trigger,),
) )
full_method = f"{row_method}:{method}" full_method = f"{row_method}:{method}"
route_trace = trace_policy.route_trace_json(
formatter="caption",
branch="metadata",
method=full_method,
input_hint=input_hint,
target=target,
detail_level=detail_level,
style_policy=style_policy,
include_trigger=include_trigger,
keep_style=keep_style,
)
return CaptionFormatRoute( return CaptionFormatRoute(
caption=caption, caption=caption,
method=full_method, method=full_method,
@@ -82,6 +99,7 @@ def naturalize_caption_result(
style_policy=style_policy, style_policy=style_policy,
include_trigger=include_trigger, include_trigger=include_trigger,
keep_style=keep_style, keep_style=keep_style,
route_trace_json=route_trace,
) )
prose, method = deps.text_to_prose(request.source_text, detail_level, keep_style) prose, method = deps.text_to_prose(request.source_text, detail_level, keep_style)
@@ -89,6 +107,17 @@ def naturalize_caption_result(
deps.with_trigger(prose, request.trigger, include_trigger), deps.with_trigger(prose, request.trigger, include_trigger),
triggers=(request.trigger,), triggers=(request.trigger,),
) )
route_trace = trace_policy.route_trace_json(
formatter="caption",
branch="text",
method=method,
input_hint=input_hint,
target=target,
detail_level=detail_level,
style_policy=style_policy,
include_trigger=include_trigger,
keep_style=keep_style,
)
return CaptionFormatRoute( return CaptionFormatRoute(
caption=caption, caption=caption,
method=method, method=method,
@@ -99,6 +128,7 @@ def naturalize_caption_result(
style_policy=style_policy, style_policy=style_policy,
include_trigger=include_trigger, include_trigger=include_trigger,
keep_style=keep_style, keep_style=keep_style,
route_trace_json=route_trace,
) )
+29
View File
@@ -395,3 +395,32 @@ def naturalize_caption(
), ),
_caption_format_dependencies(), _caption_format_dependencies(),
) )
def naturalize_caption_with_trace(
source_text: str,
metadata_json: str = "",
input_hint: str = "auto",
target: str = "auto",
trigger: str = DEFAULT_TRIGGER,
include_trigger: bool = True,
detail_level: str = "balanced",
style_policy: str = "drop_style_tail",
caption_profile: str = caption_policy.CAPTION_PROFILE_DEFAULT,
) -> tuple[str, str, str]:
"""Rewrite text like naturalize_caption and include formatter route trace JSON."""
result = caption_format_route.naturalize_caption_result(
caption_format_route.CaptionFormatRequest(
source_text=source_text,
metadata_json=metadata_json,
input_hint=input_hint,
target=target,
trigger=trigger,
include_trigger=include_trigger,
detail_level=detail_level,
style_policy=style_policy,
caption_profile=caption_profile,
),
_caption_format_dependencies(),
)
return result.as_trace_tuple()
+6 -4
View File
@@ -150,9 +150,9 @@ recoverable.
| `SxCP Prompt Builder` | category, subcategory, seed, optional config nodes | `prompt`, `negative_prompt`, `caption`, `metadata_json`, `category`, `subcategory` | | `SxCP Prompt Builder` | category, subcategory, seed, optional config nodes | `prompt`, `negative_prompt`, `caption`, `metadata_json`, `category`, `subcategory` |
| `SxCP Prompt Builder From Configs` | category/cast/profile/filter/config node outputs | Same as `SxCP Prompt Builder` | | `SxCP Prompt Builder From Configs` | category/cast/profile/filter/config node outputs | Same as `SxCP Prompt Builder` |
| `SxCP Insta/OF Prompt Pair` | options, seed_config, character_cast, location/composition/camera, hardcore_position_config | `softcore_prompt`, `hardcore_prompt`, both negatives, both captions, `shared_descriptor`, `metadata_json` | | `SxCP Insta/OF Prompt Pair` | options, seed_config, character_cast, location/composition/camera, hardcore_position_config | `softcore_prompt`, `hardcore_prompt`, both negatives, both captions, `shared_descriptor`, `metadata_json` |
| `SxCP Krea2 Formatter` | `source_text`, connectable `metadata_json`, target | `krea_prompt`, both pair prompts if pair metadata exists, negative outputs, method | | `SxCP Krea2 Formatter` | `source_text`, connectable `metadata_json`, target | `krea_prompt`, both pair prompts if pair metadata exists, negative outputs, method, `route_trace_json` |
| `SxCP SDXL Formatter` | `source_text`, connectable `metadata_json`, target, style/quality preset | `sdxl_prompt`, both pair prompts if pair metadata exists, negative outputs, method | | `SxCP SDXL Formatter` | `source_text`, connectable `metadata_json`, target, style/quality preset | `sdxl_prompt`, both pair prompts if pair metadata exists, negative outputs, method, `route_trace_json` |
| `SxCP Caption Naturalizer` | `source_text`, connectable `metadata_json`, target | `natural_caption`, method | | `SxCP Caption Naturalizer` | `source_text`, connectable `metadata_json`, target | `natural_caption`, method, `route_trace_json` |
## Practical Recipes ## Practical Recipes
@@ -703,7 +703,9 @@ structural: metadata with both a softcore side and a hardcore side
(`softcore_row`/`hardcore_row` or root soft/hard prompt/caption fields) is (`softcore_row`/`hardcore_row` or root soft/hard prompt/caption fields) is
treated as pair metadata even if the UI `mode` label is absent. Krea2, SDXL, and treated as pair metadata even if the UI `mode` label is absent. Krea2, SDXL, and
caption routes share this detection to avoid hidden drift between formatter caption routes share this detection to avoid hidden drift between formatter
paths. paths. Formatter nodes expose `route_trace_json` with the formatter name,
branch, method, normalized target/input hint, selected pair side when relevant,
and route-specific controls.
### Krea2 ### Krea2
+17
View File
@@ -0,0 +1,17 @@
from __future__ import annotations
import json
from typing import Any
def route_trace_json(**values: Any) -> str:
trace: dict[str, Any] = {}
for key, value in values.items():
if value is None:
continue
if isinstance(value, str):
value = value.strip()
if not value:
continue
trace[key] = value
return json.dumps(trace, ensure_ascii=True, sort_keys=True)
+22
View File
@@ -6,10 +6,12 @@ from typing import Any, Callable
try: try:
from . import formatter_detail as detail_policy from . import formatter_detail as detail_policy
from . import formatter_input as input_policy from . import formatter_input as input_policy
from . import formatter_route_trace as trace_policy
from . import formatter_target as target_policy from . import formatter_target as target_policy
except ImportError: # pragma: no cover - plain-script smoke tests except ImportError: # pragma: no cover - plain-script smoke tests
import formatter_detail as detail_policy import formatter_detail as detail_policy
import formatter_input as input_policy import formatter_input as input_policy
import formatter_route_trace as trace_policy
import formatter_target as target_policy import formatter_target as target_policy
@@ -68,6 +70,7 @@ def format_krea2_prompt_result(request: KreaFormatRequest, deps: KreaFormatDepen
detail_level = detail_policy.normalize_detail_level(request.detail_level) detail_level = detail_policy.normalize_detail_level(request.detail_level)
style_mode = normalize_style_mode(request.style_mode) style_mode = normalize_style_mode(request.style_mode)
target = target_policy.normalize_target(request.target) target = target_policy.normalize_target(request.target)
input_hint = input_policy.normalize_input_hint(request.input_hint, text_hint=input_policy.INPUT_HINT_PROMPT)
row, method = deps.row_from_inputs(request.source_text, request.metadata_json, request.input_hint) row, method = deps.row_from_inputs(request.source_text, request.metadata_json, request.input_hint)
if row and input_policy.is_pair_metadata(row): if row and input_policy.is_pair_metadata(row):
@@ -104,6 +107,16 @@ def format_krea2_prompt_result(request: KreaFormatRequest, deps: KreaFormatDepen
), ),
"method": f"{method}:krea2(insta_of_pair)", "method": f"{method}:krea2(insta_of_pair)",
} }
output["route_trace_json"] = trace_policy.route_trace_json(
formatter="krea2",
branch="insta_of_pair",
method=output["method"],
input_hint=input_hint,
target=target,
selected_side=pair_target.selected_side,
detail_level=detail_level,
style_mode=style_mode,
)
return KreaFormatRoute( return KreaFormatRoute(
output=output, output=output,
branch="insta_of_pair", branch="insta_of_pair",
@@ -143,6 +156,15 @@ def format_krea2_prompt_result(request: KreaFormatRequest, deps: KreaFormatDepen
"hardcore_negative_prompt": "", "hardcore_negative_prompt": "",
"method": method, "method": method,
} }
output["route_trace_json"] = trace_policy.route_trace_json(
formatter="krea2",
branch=branch,
method=method,
input_hint=input_hint,
target=target,
detail_level=detail_level,
style_mode=style_mode,
)
return KreaFormatRoute( return KreaFormatRoute(
output=output, output=output,
branch=branch, branch=branch,
+11 -7
View File
@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
try: try:
from .caption_naturalizer import naturalize_caption from .caption_naturalizer import naturalize_caption_with_trace
from .caption_policy import caption_profile_choices, style_policy_choices from .caption_policy import caption_profile_choices, style_policy_choices
from .formatter_detail import detail_level_choices from .formatter_detail import detail_level_choices
from .formatter_input import INPUT_HINT_CAPTION_OR_PROMPT, INPUT_HINT_PROMPT, input_hint_choices from .formatter_input import INPUT_HINT_CAPTION_OR_PROMPT, INPUT_HINT_PROMPT, input_hint_choices
@@ -15,7 +15,7 @@ try:
sdxl_style_preset_choices, sdxl_style_preset_choices,
) )
except ImportError: # Allows local smoke tests from the repository root. except ImportError: # Allows local smoke tests from the repository root.
from caption_naturalizer import naturalize_caption from caption_naturalizer import naturalize_caption_with_trace
from caption_policy import caption_profile_choices, style_policy_choices from caption_policy import caption_profile_choices, style_policy_choices
from formatter_detail import detail_level_choices from formatter_detail import detail_level_choices
from formatter_input import INPUT_HINT_CAPTION_OR_PROMPT, INPUT_HINT_PROMPT, input_hint_choices from formatter_input import INPUT_HINT_CAPTION_OR_PROMPT, INPUT_HINT_PROMPT, input_hint_choices
@@ -50,8 +50,8 @@ class SxCPCaptionNaturalizer:
}, },
} }
RETURN_TYPES = ("STRING", "STRING") RETURN_TYPES = ("STRING", "STRING", "STRING")
RETURN_NAMES = ("natural_caption", "method") RETURN_NAMES = ("natural_caption", "method", "route_trace_json")
FUNCTION = "build" FUNCTION = "build"
CATEGORY = "prompt_builder" CATEGORY = "prompt_builder"
@@ -69,7 +69,7 @@ class SxCPCaptionNaturalizer:
metadata_json="", metadata_json="",
): ):
active_source_text = source_text_input or source_text or "" active_source_text = source_text_input or source_text or ""
return naturalize_caption( return naturalize_caption_with_trace(
source_text=active_source_text, source_text=active_source_text,
metadata_json=metadata_json or "", metadata_json=metadata_json or "",
input_hint=input_hint, input_hint=input_hint,
@@ -103,7 +103,7 @@ class SxCPKrea2Formatter:
}, },
} }
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING") RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ( RETURN_NAMES = (
"krea_prompt", "krea_prompt",
"negative_prompt", "negative_prompt",
@@ -112,6 +112,7 @@ class SxCPKrea2Formatter:
"softcore_negative_prompt", "softcore_negative_prompt",
"hardcore_negative_prompt", "hardcore_negative_prompt",
"method", "method",
"route_trace_json",
) )
FUNCTION = "build" FUNCTION = "build"
CATEGORY = "prompt_builder" CATEGORY = "prompt_builder"
@@ -151,6 +152,7 @@ class SxCPKrea2Formatter:
row["softcore_negative_prompt"], row["softcore_negative_prompt"],
row["hardcore_negative_prompt"], row["hardcore_negative_prompt"],
row["method"], row["method"],
row["route_trace_json"],
) )
@@ -181,7 +183,7 @@ class SxCPSDXLFormatter:
}, },
} }
RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING") RETURN_TYPES = ("STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING", "STRING")
RETURN_NAMES = ( RETURN_NAMES = (
"sdxl_prompt", "sdxl_prompt",
"negative_prompt", "negative_prompt",
@@ -190,6 +192,7 @@ class SxCPSDXLFormatter:
"softcore_negative_prompt", "softcore_negative_prompt",
"hardcore_negative_prompt", "hardcore_negative_prompt",
"method", "method",
"route_trace_json",
) )
FUNCTION = "build" FUNCTION = "build"
CATEGORY = "prompt_builder" CATEGORY = "prompt_builder"
@@ -241,6 +244,7 @@ class SxCPSDXLFormatter:
row["softcore_negative_prompt"], row["softcore_negative_prompt"],
row["hardcore_negative_prompt"], row["hardcore_negative_prompt"],
row["method"], row["method"],
row["route_trace_json"],
) )
+24
View File
@@ -5,9 +5,11 @@ from typing import Any, Callable
try: try:
from . import formatter_input as input_policy from . import formatter_input as input_policy
from . import formatter_route_trace as trace_policy
from . import formatter_target as target_policy from . import formatter_target as target_policy
except ImportError: # pragma: no cover - plain-script smoke tests except ImportError: # pragma: no cover - plain-script smoke tests
import formatter_input as input_policy import formatter_input as input_policy
import formatter_route_trace as trace_policy
import formatter_target as target_policy import formatter_target as target_policy
@@ -64,6 +66,7 @@ def format_sdxl_prompt_result(request: SDXLFormatRequest, deps: SDXLFormatDepend
request.quality_preset, request.quality_preset,
) )
target = target_policy.normalize_target(request.target) target = target_policy.normalize_target(request.target)
input_hint = input_policy.normalize_input_hint(request.input_hint, text_hint=input_policy.INPUT_HINT_PROMPT)
nude_weight = max(0.1, min(3.0, float(request.nude_weight))) nude_weight = max(0.1, min(3.0, float(request.nude_weight)))
row, method = deps.row_from_inputs(request.source_text, request.metadata_json, request.input_hint) row, method = deps.row_from_inputs(request.source_text, request.metadata_json, request.input_hint)
@@ -119,6 +122,17 @@ def format_sdxl_prompt_result(request: SDXLFormatRequest, deps: SDXLFormatDepend
), ),
"method": f"{method}:sdxl(insta_of_pair)", "method": f"{method}:sdxl(insta_of_pair)",
} }
output["route_trace_json"] = trace_policy.route_trace_json(
formatter="sdxl",
branch="insta_of_pair",
method=output["method"],
input_hint=input_hint,
target=target,
selected_side=pair_target.selected_side,
style_preset=style_preset,
quality_preset=quality_preset,
nude_weight=nude_weight,
)
return SDXLFormatRoute( return SDXLFormatRoute(
output=output, output=output,
branch="insta_of_pair", branch="insta_of_pair",
@@ -163,6 +177,16 @@ def format_sdxl_prompt_result(request: SDXLFormatRequest, deps: SDXLFormatDepend
"hardcore_negative_prompt": "", "hardcore_negative_prompt": "",
"method": method, "method": method,
} }
output["route_trace_json"] = trace_policy.route_trace_json(
formatter="sdxl",
branch=branch,
method=method,
input_hint=input_hint,
target=target,
style_preset=style_preset,
quality_preset=quality_preset,
nude_weight=nude_weight,
)
return SDXLFormatRoute( return SDXLFormatRoute(
output=output, output=output,
branch=branch, branch=branch,
+53 -2
View File
@@ -3150,6 +3150,10 @@ def smoke_krea_format_route_policy() -> None:
_expect(typed_single.output == public_single, "Typed Krea format route should match public single formatter output") _expect(typed_single.output == public_single, "Typed Krea format route should match public single formatter output")
_expect(typed_single.branch == "metadata(single)", "Typed Krea format route changed single branch") _expect(typed_single.branch == "metadata(single)", "Typed Krea format route changed single branch")
_expect(typed_single.target == "single", "Typed Krea format route lost target normalization") _expect(typed_single.target == "single", "Typed Krea format route lost target normalization")
single_trace = json.loads(_expect_text("krea_format_route_policy.single_trace", typed_single.output.get("route_trace_json"), 20))
_expect(single_trace.get("formatter") == "krea2", "Typed Krea single trace lost formatter")
_expect(single_trace.get("branch") == "metadata(single)", "Typed Krea single trace lost branch")
_expect(single_trace.get("target") == "single", "Typed Krea single trace lost target")
_expect("krea route marker" in typed_single.output.get("krea_prompt", ""), "Typed Krea route lost extra positive") _expect("krea route marker" in typed_single.output.get("krea_prompt", ""), "Typed Krea route lost extra positive")
_expect("krea route negative" in typed_single.output.get("negative_prompt", ""), "Typed Krea route lost extra negative") _expect("krea route negative" in typed_single.output.get("negative_prompt", ""), "Typed Krea route lost extra negative")
@@ -3191,6 +3195,9 @@ def smoke_krea_format_route_policy() -> None:
) )
_expect(typed_pair.output == public_pair, "Typed Krea format route should match public pair formatter output") _expect(typed_pair.output == public_pair, "Typed Krea format route should match public pair formatter output")
_expect(typed_pair.branch == "insta_of_pair", "Typed Krea format route changed pair branch") _expect(typed_pair.branch == "insta_of_pair", "Typed Krea format route changed pair branch")
pair_trace = json.loads(_expect_text("krea_format_route_policy.pair_trace", typed_pair.output.get("route_trace_json"), 20))
_expect(pair_trace.get("branch") == "insta_of_pair", "Typed Krea pair trace lost branch")
_expect(pair_trace.get("selected_side") == "hardcore", "Typed Krea pair trace lost selected side")
_expect_text("krea_format_route_policy.hard_prompt", typed_pair.output.get("krea_hardcore_prompt"), 40) _expect_text("krea_format_route_policy.hard_prompt", typed_pair.output.get("krea_hardcore_prompt"), 40)
_expect("pair route marker" in typed_pair.output.get("krea_prompt", ""), "Typed Krea pair route lost extra positive") _expect("pair route marker" in typed_pair.output.get("krea_prompt", ""), "Typed Krea pair route lost extra positive")
@@ -3217,6 +3224,9 @@ def smoke_krea_format_route_policy() -> None:
_expect(typed_fallback.output == public_fallback, "Typed Krea format route should match public fallback output") _expect(typed_fallback.output == public_fallback, "Typed Krea format route should match public fallback output")
_expect(typed_fallback.branch == "fallback", "Typed Krea format route changed fallback branch") _expect(typed_fallback.branch == "fallback", "Typed Krea format route changed fallback branch")
_expect(typed_fallback.target == "auto", "Typed Krea format route should normalize invalid target") _expect(typed_fallback.target == "auto", "Typed Krea format route should normalize invalid target")
fallback_trace = json.loads(_expect_text("krea_format_route_policy.fallback_trace", typed_fallback.output.get("route_trace_json"), 20))
_expect(fallback_trace.get("branch") == "fallback", "Typed Krea fallback trace lost branch")
_expect(fallback_trace.get("input_hint") == "prompt", "Typed Krea fallback trace lost input hint")
_expect(typed_fallback.detail_level == "balanced", "Typed Krea format route should normalize invalid detail level") _expect(typed_fallback.detail_level == "balanced", "Typed Krea format route should normalize invalid detail level")
_expect(typed_fallback.style_mode == "preserve", "Typed Krea format route should normalize invalid style mode") _expect(typed_fallback.style_mode == "preserve", "Typed Krea format route should normalize invalid style mode")
_expect(krea_format_route.style_mode_choices() == ["preserve", "photographic", "minimal"], "Krea style mode choices changed") _expect(krea_format_route.style_mode_choices() == ["preserve", "photographic", "minimal"], "Krea style mode choices changed")
@@ -3378,6 +3388,22 @@ def smoke_caption_format_route_policy() -> None:
_expect(typed_metadata.detail_level == "dense", "Typed caption route lost training_dense detail override") _expect(typed_metadata.detail_level == "dense", "Typed caption route lost training_dense detail override")
_expect(typed_metadata.style_policy == "drop_style_tail", "Typed caption route lost training_dense style override") _expect(typed_metadata.style_policy == "drop_style_tail", "Typed caption route lost training_dense style override")
_expect(typed_metadata.include_trigger is True, "Typed caption route lost training_dense trigger override") _expect(typed_metadata.include_trigger is True, "Typed caption route lost training_dense trigger override")
metadata_trace = json.loads(_expect_text("caption_format_route_policy.metadata_trace", typed_metadata.route_trace_json, 20))
_expect(metadata_trace.get("formatter") == "caption", "Typed caption metadata trace lost formatter")
_expect(metadata_trace.get("branch") == "metadata", "Typed caption metadata trace lost branch")
_expect(metadata_trace.get("target") == "single", "Typed caption metadata trace lost target")
traced_public_metadata = caption_naturalizer.naturalize_caption_with_trace(
"",
metadata_json=metadata_request.metadata_json,
input_hint=metadata_request.input_hint,
target=metadata_request.target,
trigger=metadata_request.trigger,
include_trigger=metadata_request.include_trigger,
detail_level=metadata_request.detail_level,
style_policy=metadata_request.style_policy,
caption_profile=metadata_request.caption_profile,
)
_expect(traced_public_metadata == typed_metadata.as_trace_tuple(), "Caption trace wrapper drifted from typed route")
_expect(typed_metadata.caption.startswith(Trigger), "Typed caption metadata route should prepend training trigger") _expect(typed_metadata.caption.startswith(Trigger), "Typed caption metadata route should prepend training trigger")
fallback_request = caption_format_route.CaptionFormatRequest( fallback_request = caption_format_route.CaptionFormatRequest(
@@ -3409,6 +3435,9 @@ def smoke_caption_format_route_policy() -> None:
_expect(typed_fallback.input_hint == "auto", "Typed caption route should normalize invalid input hint") _expect(typed_fallback.input_hint == "auto", "Typed caption route should normalize invalid input hint")
_expect(typed_fallback.target == "auto", "Typed caption route should normalize invalid target") _expect(typed_fallback.target == "auto", "Typed caption route should normalize invalid target")
_expect(typed_fallback.include_trigger is False, "Typed caption browsing profile should disable trigger") _expect(typed_fallback.include_trigger is False, "Typed caption browsing profile should disable trigger")
fallback_trace = json.loads(_expect_text("caption_format_route_policy.fallback_trace", typed_fallback.route_trace_json, 20))
_expect(fallback_trace.get("branch") == "text", "Typed caption fallback trace lost branch")
_expect(fallback_trace.get("input_hint") == "auto", "Typed caption fallback trace lost input hint")
_expect(typed_fallback.keep_style is True, "Typed caption browsing profile should keep style terms") _expect(typed_fallback.keep_style is True, "Typed caption browsing profile should keep style terms")
_expect(not typed_fallback.caption.startswith(Trigger), "Typed caption fallback route should not prepend browsing trigger") _expect(not typed_fallback.caption.startswith(Trigger), "Typed caption fallback route should not prepend browsing trigger")
_expect(typed_fallback.method == "text(fallback)", "Typed caption fallback method changed") _expect(typed_fallback.method == "text(fallback)", "Typed caption fallback method changed")
@@ -3721,6 +3750,11 @@ def smoke_sdxl_format_route_policy() -> None:
_expect(typed_single.target == "single", "Typed SDXL format route lost target normalization") _expect(typed_single.target == "single", "Typed SDXL format route lost target normalization")
_expect(typed_single.nude_weight == 3.0, "Typed SDXL format route should clamp high nude weight") _expect(typed_single.nude_weight == 3.0, "Typed SDXL format route should clamp high nude weight")
_expect(typed_single.style_preset == "photographic", "Typed SDXL format route lost profile style override") _expect(typed_single.style_preset == "photographic", "Typed SDXL format route lost profile style override")
single_trace = json.loads(_expect_text("sdxl_format_route_policy.single_trace", typed_single.output.get("route_trace_json"), 20))
_expect(single_trace.get("formatter") == "sdxl", "Typed SDXL single trace lost formatter")
_expect(single_trace.get("branch") == "metadata", "Typed SDXL single trace lost branch")
_expect(single_trace.get("target") == "single", "Typed SDXL single trace lost target")
_expect(single_trace.get("nude_weight") == 3.0, "Typed SDXL single trace lost clamped nude weight")
_expect("sdxl route marker" in typed_single.output.get("sdxl_prompt", ""), "Typed SDXL route lost extra positive") _expect("sdxl route marker" in typed_single.output.get("sdxl_prompt", ""), "Typed SDXL route lost extra positive")
_expect("sdxl route negative" in typed_single.output.get("negative_prompt", ""), "Typed SDXL route lost extra negative") _expect("sdxl route negative" in typed_single.output.get("negative_prompt", ""), "Typed SDXL route lost extra negative")
@@ -3762,6 +3796,9 @@ def smoke_sdxl_format_route_policy() -> None:
) )
_expect(typed_pair.output == public_pair, "Typed SDXL format route should match public pair formatter output") _expect(typed_pair.output == public_pair, "Typed SDXL format route should match public pair formatter output")
_expect(typed_pair.branch == "insta_of_pair", "Typed SDXL format route changed pair branch") _expect(typed_pair.branch == "insta_of_pair", "Typed SDXL format route changed pair branch")
pair_trace = json.loads(_expect_text("sdxl_format_route_policy.pair_trace", typed_pair.output.get("route_trace_json"), 20))
_expect(pair_trace.get("branch") == "insta_of_pair", "Typed SDXL pair trace lost branch")
_expect(pair_trace.get("selected_side") == "hardcore", "Typed SDXL pair trace lost selected side")
_expect_text("sdxl_format_route_policy.hard_prompt", typed_pair.output.get("sdxl_hardcore_prompt"), 40) _expect_text("sdxl_format_route_policy.hard_prompt", typed_pair.output.get("sdxl_hardcore_prompt"), 40)
_expect("pair sdxl route marker" in typed_pair.output.get("sdxl_prompt", ""), "Typed SDXL pair route lost extra positive") _expect("pair sdxl route marker" in typed_pair.output.get("sdxl_prompt", ""), "Typed SDXL pair route lost extra positive")
@@ -3793,6 +3830,9 @@ def smoke_sdxl_format_route_policy() -> None:
_expect(typed_fallback.branch == "fallback", "Typed SDXL format route changed fallback branch") _expect(typed_fallback.branch == "fallback", "Typed SDXL format route changed fallback branch")
_expect(typed_fallback.target == "auto", "Typed SDXL format route should normalize invalid target") _expect(typed_fallback.target == "auto", "Typed SDXL format route should normalize invalid target")
_expect(typed_fallback.nude_weight == 0.1, "Typed SDXL format route should clamp low nude weight") _expect(typed_fallback.nude_weight == 0.1, "Typed SDXL format route should clamp low nude weight")
fallback_trace = json.loads(_expect_text("sdxl_format_route_policy.fallback_trace", typed_fallback.output.get("route_trace_json"), 20))
_expect(fallback_trace.get("branch") == "fallback", "Typed SDXL fallback trace lost branch")
_expect(fallback_trace.get("input_hint") == "prompt", "Typed SDXL fallback trace lost input hint")
_expect("Characters:" not in typed_fallback.output.get("sdxl_prompt", ""), "Typed SDXL fallback leaked Characters label") _expect("Characters:" not in typed_fallback.output.get("sdxl_prompt", ""), "Typed SDXL fallback leaked Characters label")
_expect("blur" in typed_fallback.output.get("negative_prompt", ""), "Typed SDXL fallback route lost Avoid negative") _expect("blur" in typed_fallback.output.get("negative_prompt", ""), "Typed SDXL fallback route lost Avoid negative")
@@ -7005,7 +7045,7 @@ def smoke_node_formatter_registration() -> None:
_expect(krea_optional["metadata_json"][1].get("forceInput") is True, "Krea2 Formatter metadata_json should be connectable") _expect(krea_optional["metadata_json"][1].get("forceInput") is True, "Krea2 Formatter metadata_json should be connectable")
_expect(krea_optional["negative_prompt"][1].get("forceInput") is True, "Krea2 Formatter negative_prompt should be connectable") _expect(krea_optional["negative_prompt"][1].get("forceInput") is True, "Krea2 Formatter negative_prompt should be connectable")
caption, caption_method = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build( caption, caption_method, caption_trace_json = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"]().build(
"A woman standing by a window, best quality", "A woman standing by a window, best quality",
"caption_or_prompt", "caption_or_prompt",
"manual_controls", "manual_controls",
@@ -7017,6 +7057,9 @@ def smoke_node_formatter_registration() -> None:
_expect_text("node_formatter.caption", caption, 20) _expect_text("node_formatter.caption", caption, 20)
_expect(caption.startswith("sxcppnl7"), "Caption Naturalizer did not prepend trigger") _expect(caption.startswith("sxcppnl7"), "Caption Naturalizer did not prepend trigger")
_expect("text(" in caption_method, "Caption Naturalizer method changed unexpectedly") _expect("text(" in caption_method, "Caption Naturalizer method changed unexpectedly")
caption_trace = json.loads(_expect_text("node_formatter.caption_trace", caption_trace_json, 20))
_expect(caption_trace.get("formatter") == "caption", "Caption Naturalizer trace lost formatter")
_expect(caption_trace.get("branch") == "text", "Caption Naturalizer trace lost text branch")
caption_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"].INPUT_TYPES().get("required") or {} caption_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPCaptionNaturalizer"].INPUT_TYPES().get("required") or {}
_expect("caption_profile" in caption_inputs, "Caption Naturalizer lost caption_profile input") _expect("caption_profile" in caption_inputs, "Caption Naturalizer lost caption_profile input")
_expect("target" in caption_inputs, "Caption Naturalizer lost target input") _expect("target" in caption_inputs, "Caption Naturalizer lost target input")
@@ -7039,6 +7082,9 @@ def smoke_node_formatter_registration() -> None:
_expect_text("node_formatter.krea_prompt", krea_output[0], 20) _expect_text("node_formatter.krea_prompt", krea_output[0], 20)
_expect("sxcppnl7" in krea_output[0], "Krea2 Formatter did not preserve trigger when enabled") _expect("sxcppnl7" in krea_output[0], "Krea2 Formatter did not preserve trigger when enabled")
_expect(krea_output[6].startswith("text("), "Krea2 Formatter method changed unexpectedly") _expect(krea_output[6].startswith("text("), "Krea2 Formatter method changed unexpectedly")
krea_trace = json.loads(_expect_text("node_formatter.krea_trace", krea_output[7], 20))
_expect(krea_trace.get("formatter") == "krea2", "Krea2 Formatter trace lost formatter")
_expect(krea_trace.get("branch") == "fallback", "Krea2 Formatter trace lost fallback branch")
sdxl_output = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"]().build( sdxl_output = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"]().build(
"A woman standing by a window", "A woman standing by a window",
@@ -7056,6 +7102,9 @@ def smoke_node_formatter_registration() -> None:
_expect_trigger_once("node_formatter.sdxl_prompt", sdxl_output[0], "mythp0rt") _expect_trigger_once("node_formatter.sdxl_prompt", sdxl_output[0], "mythp0rt")
_expect_text("node_formatter.sdxl_negative", sdxl_output[1], 20) _expect_text("node_formatter.sdxl_negative", sdxl_output[1], 20)
_expect(sdxl_output[6].startswith("text("), "SDXL Formatter method changed unexpectedly") _expect(sdxl_output[6].startswith("text("), "SDXL Formatter method changed unexpectedly")
sdxl_trace = json.loads(_expect_text("node_formatter.sdxl_trace", sdxl_output[7], 20))
_expect(sdxl_trace.get("formatter") == "sdxl", "SDXL Formatter trace lost formatter")
_expect(sdxl_trace.get("branch") == "fallback", "SDXL Formatter trace lost fallback branch")
sdxl_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"].INPUT_TYPES().get("required") or {} sdxl_inputs = sxcp_nodes.NODE_CLASS_MAPPINGS["SxCPSDXLFormatter"].INPUT_TYPES().get("required") or {}
_expect("formatter_profile" in sdxl_inputs, "SDXL Formatter lost formatter_profile input") _expect("formatter_profile" in sdxl_inputs, "SDXL Formatter lost formatter_profile input")
_expect(sdxl_inputs["target"][0] == formatter_target.target_choices(), "SDXL Formatter target choices drifted") _expect(sdxl_inputs["target"][0] == formatter_target.target_choices(), "SDXL Formatter target choices drifted")
@@ -7108,6 +7157,7 @@ def smoke_node_formatter_registration() -> None:
expected_krea_pair["softcore_negative_prompt"], expected_krea_pair["softcore_negative_prompt"],
expected_krea_pair["hardcore_negative_prompt"], expected_krea_pair["hardcore_negative_prompt"],
expected_krea_pair["method"], expected_krea_pair["method"],
expected_krea_pair["route_trace_json"],
), ),
"Krea2 Formatter node output drifted from public metadata formatter", "Krea2 Formatter node output drifted from public metadata formatter",
) )
@@ -7152,11 +7202,12 @@ def smoke_node_formatter_registration() -> None:
expected_sdxl_pair["softcore_negative_prompt"], expected_sdxl_pair["softcore_negative_prompt"],
expected_sdxl_pair["hardcore_negative_prompt"], expected_sdxl_pair["hardcore_negative_prompt"],
expected_sdxl_pair["method"], expected_sdxl_pair["method"],
expected_sdxl_pair["route_trace_json"],
), ),
"SDXL Formatter node output drifted from public metadata formatter", "SDXL Formatter node output drifted from public metadata formatter",
) )
expected_caption_pair = caption_naturalizer.naturalize_caption( expected_caption_pair = caption_naturalizer.naturalize_caption_with_trace(
"", "",
metadata_json=pair_metadata, metadata_json=pair_metadata,
input_hint="metadata_json", input_hint="metadata_json",