From 333fa5eae65a6bf22139eb0afab08a9bac3fe9c6 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 13:56:21 +0200 Subject: [PATCH] Centralize formatter detail levels --- caption_policy.py | 11 ++++----- docs/prompt-architecture-improvement-plan.md | 9 ++++++++ docs/prompt-pool-routing-map.md | 1 + formatter_detail.py | 23 +++++++++++++++++++ krea_format_route.py | 4 +++- node_formatter.py | 6 +++-- tools/prompt_map_audit.py | 1 + tools/prompt_smoke.py | 24 ++++++++++++++++++++ 8 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 formatter_detail.py diff --git a/caption_policy.py b/caption_policy.py index b80d1c7..2becb69 100644 --- a/caption_policy.py +++ b/caption_policy.py @@ -4,9 +4,11 @@ import re from typing import Any try: + from . import formatter_detail as detail_policy from . import formatter_input as input_policy from . import route_metadata as route_metadata_policy except ImportError: # Allows local smoke tests with `python tools/prompt_smoke.py`. + import formatter_detail as detail_policy import formatter_input as input_policy import route_metadata as route_metadata_policy @@ -14,7 +16,7 @@ except ImportError: # Allows local smoke tests with `python tools/prompt_smoke. OLD_TRIGGER = "sxcpinup_coloredpencil" DEFAULT_TRIGGER = "sxcppnl7" -DETAIL_LEVELS = ("balanced", "concise", "dense") +DETAIL_LEVELS = detail_policy.DETAIL_LEVELS STYLE_POLICIES = ("drop_style_tail", "keep_style_terms") CAPTION_PROFILE_DEFAULT = "manual_controls" @@ -72,7 +74,7 @@ POSITION_FAMILY_CAPTION_LABELS = { def normalize_detail_level(value: str) -> str: - return value if value in DETAIL_LEVELS else "balanced" + return detail_policy.normalize_detail_level(value) def normalize_style_policy(value: str) -> str: @@ -107,10 +109,7 @@ def keep_style_terms(style_policy: str) -> bool: def detail_allows(level: str, dense_only: bool = False) -> bool: - level = normalize_detail_level((level or "balanced").strip().lower()) - if dense_only: - return level == "dense" - return level != "concise" + return detail_policy.detail_allows(level, dense_only=dense_only) def strip_style_tail(text: str) -> str: diff --git a/docs/prompt-architecture-improvement-plan.md b/docs/prompt-architecture-improvement-plan.md index bd2d255..9ff63e5 100644 --- a/docs/prompt-architecture-improvement-plan.md +++ b/docs/prompt-architecture-improvement-plan.md @@ -84,6 +84,15 @@ routes: It must not make formatter-style decisions. Krea prose, SDXL tags, and training caption sentence shape stay in their formatter modules. +Formatter detail-level handling now has one home: + +- `formatter_detail.py` + +It owns route-neutral prose detail levels, node choice lists, normalization, and +the concise/balanced/dense inclusion gate used by Krea2 and natural-caption +routes. It must not own route-specific style controls such as Krea photographic +mode or caption style-tail policy. + Formatter target handling now has one home: - `formatter_target.py` diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index bee9032..920dfb9 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -126,6 +126,7 @@ Core helper ownership: | `krea_cast.py` | Shared formatter cast descriptor parsing, cast labels, cast prose, natural cast descriptor text, and label replacement used by Krea2 and caption routes. | | `prompt_hygiene.py` | Generic prompt, caption, and negative-prompt cleanup. | | `row_normalization.py` | Final prompt-row and pair metadata normalization: trigger prepending, extra-positive append, negative merge/dedupe, caption-part joining, embedded soft/hard row output and side-metadata synchronization, and embedded row sanitation. | +| `formatter_detail.py` | Shared formatter detail-level choices, normalization, and concise/balanced/dense gates used by Krea2 and caption routes. | | `formatter_input.py` | Shared formatter input parsing: text cleanup, metadata/source JSON detection, trigger-prefix stripping, shared prompt field-label inventory, fallback field-label stripping, `Avoid:` splitting, prompt-field extraction, and metadata row-value fallback. | | `formatter_target.py` | Shared formatter target choices and normalization for `auto`, `single`, `softcore`, and `hardcore`, including pair-side selection and combined-caption inclusion policy. | | `node_tooltips.py` | Node input tooltip inventory, node-specific overrides, dynamic-input fallback rules, and tooltip injection installer used by `__init__.py`. | diff --git a/formatter_detail.py b/formatter_detail.py new file mode 100644 index 0000000..39b7ecf --- /dev/null +++ b/formatter_detail.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Any + + +DETAIL_LEVELS = ("balanced", "concise", "dense") +DEFAULT_DETAIL_LEVEL = "balanced" + + +def detail_level_choices() -> list[str]: + return list(DETAIL_LEVELS) + + +def normalize_detail_level(value: Any) -> str: + level = str(value or "").strip().lower().replace("-", "_").replace(" ", "_") + return level if level in DETAIL_LEVELS else DEFAULT_DETAIL_LEVEL + + +def detail_allows(level: Any, dense_only: bool = False) -> bool: + level = normalize_detail_level(level) + if dense_only: + return level == "dense" + return level != "concise" diff --git a/krea_format_route.py b/krea_format_route.py index d12a4f6..bb6a22c 100644 --- a/krea_format_route.py +++ b/krea_format_route.py @@ -4,8 +4,10 @@ from dataclasses import dataclass from typing import Any, Callable try: + from . import formatter_detail as detail_policy from . import formatter_target as target_policy except ImportError: # pragma: no cover - plain-script smoke tests + import formatter_detail as detail_policy import formatter_target as target_policy @@ -48,7 +50,7 @@ class KreaFormatDependencies: def format_krea2_prompt_result(request: KreaFormatRequest, deps: KreaFormatDependencies) -> KreaFormatRoute: - detail_level = request.detail_level if request.detail_level in ("concise", "balanced", "dense") else "balanced" + detail_level = detail_policy.normalize_detail_level(request.detail_level) style_mode = request.style_mode if request.style_mode in ("preserve", "photographic", "minimal") else "preserve" target = target_policy.normalize_target(request.target) row, method = deps.row_from_inputs(request.source_text, request.metadata_json, request.input_hint) diff --git a/node_formatter.py b/node_formatter.py index 55ae88e..8d4a240 100644 --- a/node_formatter.py +++ b/node_formatter.py @@ -3,6 +3,7 @@ from __future__ import annotations try: from .caption_naturalizer import naturalize_caption from .caption_policy import caption_profile_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_target import target_choices from .krea_formatter import format_krea2_prompt @@ -15,6 +16,7 @@ try: except ImportError: # Allows local smoke tests from the repository root. from caption_naturalizer import naturalize_caption from caption_policy import caption_profile_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_target import target_choices from krea_formatter import format_krea2_prompt @@ -34,7 +36,7 @@ class SxCPCaptionNaturalizer: "source_text": ("STRING", {"default": "", "multiline": True}), "input_hint": (input_hint_choices(text_hint=INPUT_HINT_CAPTION_OR_PROMPT), {"default": "auto"}), "caption_profile": (caption_profile_choices(), {"default": "manual_controls"}), - "detail_level": (["balanced", "concise", "dense"], {"default": "balanced"}), + "detail_level": (detail_level_choices(), {"default": "balanced"}), "style_policy": (["drop_style_tail", "keep_style_terms"], {"default": "drop_style_tail"}), "trigger": ("STRING", {"default": "sxcppnl7"}), "include_trigger": ("BOOLEAN", {"default": True}), @@ -86,7 +88,7 @@ class SxCPKrea2Formatter: "source_text": ("STRING", {"default": "", "multiline": True}), "input_hint": (input_hint_choices(text_hint=INPUT_HINT_PROMPT), {"default": "auto"}), "target": (target_choices(), {"default": "auto"}), - "detail_level": (["balanced", "concise", "dense"], {"default": "balanced"}), + "detail_level": (detail_level_choices(), {"default": "balanced"}), "style_mode": (["preserve", "photographic", "minimal"], {"default": "preserve"}), "preserve_trigger": ("BOOLEAN", {"default": False}), }, diff --git a/tools/prompt_map_audit.py b/tools/prompt_map_audit.py index c59cec4..5b2a066 100644 --- a/tools/prompt_map_audit.py +++ b/tools/prompt_map_audit.py @@ -38,6 +38,7 @@ CRITICAL_ROUTE_MODULES: tuple[tuple[str, str], ...] = ( ("krea_format_route.py", "krea_format_route_policy"), ("sdxl_format_route.py", "sdxl_format_route_policy"), ("caption_format_route.py", "caption_format_route_policy"), + ("formatter_detail.py", "formatter_detail_policy"), ("formatter_input.py", "formatter_input_policy"), ("formatter_target.py", "formatter_target_policy"), ("pair_builder.py", "pair_builder_policy"), diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 8896e3d..2c753a1 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -41,6 +41,7 @@ import character_slot # noqa: E402 import category_cast_config # noqa: E402 import category_library # noqa: E402 import filter_config # noqa: E402 +import formatter_detail # noqa: E402 import formatter_input # noqa: E402 import formatter_target # noqa: E402 import hardcore_position_config # noqa: E402 @@ -2842,6 +2843,26 @@ def smoke_formatter_target_policy() -> None: _expect(not hard_pair.include_softcore and hard_pair.include_hardcore, "Pair hardcore should include only hard side") +def smoke_formatter_detail_policy() -> None: + _expect( + formatter_detail.detail_level_choices() == ["balanced", "concise", "dense"], + "Formatter detail choices changed", + ) + _expect(formatter_detail.normalize_detail_level("dense") == "dense", "Formatter detail lost dense") + _expect(formatter_detail.normalize_detail_level("BAD") == "balanced", "Formatter detail should normalize invalid values") + _expect(formatter_detail.detail_allows("concise") is False, "Formatter detail concise gate changed") + _expect(formatter_detail.detail_allows("dense", dense_only=True) is True, "Formatter detail dense-only gate changed") + _expect(caption_policy.DETAIL_LEVELS is formatter_detail.DETAIL_LEVELS, "Caption detail levels should delegate") + _expect( + caption_policy.normalize_detail_level("bad") == formatter_detail.normalize_detail_level("bad"), + "Caption detail normalization should delegate", + ) + _expect( + caption_policy.detail_allows("dense", dense_only=True) == formatter_detail.detail_allows("dense", dense_only=True), + "Caption detail gate should delegate", + ) + + def smoke_krea_format_route_policy() -> None: row = _prompt_row( name="krea_format_route_single", @@ -6264,8 +6285,10 @@ def smoke_node_formatter_registration() -> None: _expect("caption_profile" in caption_inputs, "Caption Naturalizer lost caption_profile input") _expect("target" in caption_inputs, "Caption Naturalizer lost target input") _expect(caption_inputs["target"][0] == formatter_target.target_choices(), "Caption Naturalizer target choices drifted") + _expect(caption_inputs["detail_level"][0] == formatter_detail.detail_level_choices(), "Caption Naturalizer detail choices drifted") _expect("tooltip" in caption_inputs["caption_profile"][1], "Caption profile tooltip injection missing") _expect(krea_inputs["target"][0] == formatter_target.target_choices(), "Krea2 Formatter target choices drifted") + _expect(krea_inputs["detail_level"][0] == formatter_detail.detail_level_choices(), "Krea2 Formatter detail choices drifted") krea_output = krea_node().build( "sxcppnl7 A woman standing by a window", @@ -6644,6 +6667,7 @@ SMOKE_CASES: list[tuple[str, Callable[[], None]]] = [ ("row_assembly_policy", smoke_row_assembly_policy), ("formatter_input_policy", smoke_formatter_input_policy), ("formatter_target_policy", smoke_formatter_target_policy), + ("formatter_detail_policy", smoke_formatter_detail_policy), ("krea_format_route_policy", smoke_krea_format_route_policy), ("formatter_cast_policy", smoke_formatter_cast_policy), ("caption_policy", smoke_caption_policy),