From 7c615bdf7b104bb7321e625f20e761e87a9afbf8 Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 27 Jun 2026 17:18:57 +0200 Subject: [PATCH] Keep SDXL explicit tags metadata driven --- docs/prompt-pool-routing-map.md | 4 +- sdxl_tag_routes.py | 36 +++++++++++++- tools/prompt_smoke.py | 84 +++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/docs/prompt-pool-routing-map.md b/docs/prompt-pool-routing-map.md index 9aef8bb..92e64cb 100644 --- a/docs/prompt-pool-routing-map.md +++ b/docs/prompt-pool-routing-map.md @@ -802,9 +802,9 @@ SDXL field consumption: | Branch | Reads most from | Key functions | | --- | --- | --- | -| Normal metadata | cast descriptors, age/body/skin/hair/eyes, `action_family`, `position_family`, `position_keys`, item, role graph, scene, camera config/directive | `sdxl_tag_routes.row_core_tags_result`, `sdxl_tag_policy.metadata_family_tags`, `sdxl_tag_policy.camera_tags` | +| Normal metadata | cast descriptors, age/body/skin/hair/eyes, `action_family`, `position_family`, `position_keys`, item, role graph, clothing/body exposure state, scene, camera config/directive | `sdxl_tag_routes.row_core_tags_result`, `sdxl_tag_policy.metadata_family_tags`, `sdxl_tag_policy.camera_tags` | | Pair softcore | `softcore_row`, pair partner styling, root soft camera config | `sdxl_tag_routes.soft_tags_result` | -| Pair hardcore | `hardcore_row`, `action_family`, `position_family`, `position_keys`, `hardcore_clothing_state`, hard camera fields, hard prompt text | `sdxl_tag_routes.hard_tags_result`, `sdxl_tag_policy.metadata_family_tags` | +| Pair hardcore | `hardcore_row`, `action_family`, `position_family`, `position_keys`, role graph, item, `hardcore_clothing_state`, expression/composition, hard camera fields | `sdxl_tag_routes.hard_tags_result`, `sdxl_tag_policy.metadata_family_tags` | | Text fallback | `source_text`, preserve-trigger setting, shared field-label stripping | `_fallback_text_to_sdxl` | SDXL is the right place for model trigger handling, tag ordering, weight syntax, diff --git a/sdxl_tag_routes.py b/sdxl_tag_routes.py index 4dcbdd6..afc37f1 100644 --- a/sdxl_tag_routes.py +++ b/sdxl_tag_routes.py @@ -70,6 +70,30 @@ def _pair_counts(row: dict[str, Any], root: dict[str, Any]) -> tuple[int, int]: return _descriptor_counts(root) +def _row_explicit_signal_text( + row: dict[str, Any], + *, + item: str, + pose: str, + role_graph: str, + expression: str, + composition: str, + deps: SDXLTagRouteDependencies, +) -> str: + values = ( + item, + pose, + role_graph, + deps.clean(row.get("hardcore_clothing_state")), + deps.clean(row.get("clothing_state")), + deps.clean(row.get("clothing")), + deps.clean(row.get("scene_kind")), + expression, + composition, + ) + return " ".join(deps.clean(value) for value in values if deps.clean(value)) + + def row_core_tags_result(request: SDXLRowTagRequest, deps: SDXLTagRouteDependencies) -> SDXLTagRoute: row = request.row tags: list[str] = [] @@ -110,7 +134,15 @@ def row_core_tags_result(request: SDXLRowTagRequest, deps: SDXLTagRouteDependenc for tag in deps.camera_tags(row): deps.add_one(tags, seen, tag) - combined = " ".join(deps.clean(value) for value in (item, pose, role_graph, row.get("prompt", ""))) + combined = _row_explicit_signal_text( + row, + item=item, + pose=pose, + role_graph=role_graph, + expression=expression, + composition=composition, + deps=deps, + ) for tag in deps.explicit_tags(combined, request.nude_weight): deps.add_one(tags, seen, tag) return SDXLTagRoute(tags) @@ -191,7 +223,7 @@ def hard_tags_result(request: SDXLPairTagRequest, deps: SDXLTagRouteDependencies deps.add(tags, seen, value) for tag in deps.camera_tags(row, root.get("hardcore_camera_directive"), root.get("hardcore_camera_config")): deps.add_one(tags, seen, tag) - combined = " ".join([hard_role, hard_item, hard_clothing, expression, composition, root.get("hardcore_prompt", "") or ""]) + combined = " ".join([hard_role, hard_item, hard_clothing, expression, composition]) for tag in deps.explicit_tags(combined, request.nude_weight): deps.add_one(tags, seen, tag) return SDXLTagRoute(tags) diff --git a/tools/prompt_smoke.py b/tools/prompt_smoke.py index 8876a20..676230b 100644 --- a/tools/prompt_smoke.py +++ b/tools/prompt_smoke.py @@ -4435,6 +4435,43 @@ def smoke_sdxl_tag_routes() -> None: "Typed SDXL row tag route should match legacy wrapper output", ) _expect("sdxl route tag" in typed_row.as_text(), "Typed SDXL row tag route lost route-specific formatter hint") + stale_prompt_row = _fixture_hardcore_row( + prompt="stale raw prompt mentions fully nude naked pussy penis oral anal semen penetration", + item="standing portrait setup", + pose="standing pose", + role_graph="", + source_role_graph="", + expression="neutral expression", + action_family="", + position_family="", + position_key="", + position_keys=[], + ) + stale_tags = sdxl_tag_routes.row_core_tags_result( + sdxl_tag_routes.SDXLRowTagRequest(stale_prompt_row, 1.29), + deps, + ).as_text() + for forbidden in ("(naked:", "pussy", "penis", "oral sex", "anal sex", "semen", "penetration"): + _expect(forbidden not in stale_tags, f"SDXL row tags should not infer explicit tag from stale prompt: {forbidden}") + + metadata_explicit_row = _fixture_hardcore_row( + prompt="stale raw prompt without explicit tag anchors", + item="kneeling pose with visible penis and pussy contact", + pose="penetration pose", + role_graph="penis thrusts into pussy", + source_role_graph="penis thrusts into pussy", + hardcore_clothing_state="fully nude body, bare skin unobstructed", + action_family="penetration", + position_family="penetrative", + position_key="kneeling", + position_keys=["kneeling"], + ) + metadata_tags = sdxl_tag_routes.row_core_tags_result( + sdxl_tag_routes.SDXLRowTagRequest(metadata_explicit_row, 1.29), + deps, + ).as_text() + for required in ("(naked:1.29)", "pussy", "penis", "penetration"): + _expect(required in metadata_tags, f"SDXL row tags lost structured explicit metadata tag: {required}") pair = pb.build_insta_of_pair( row_number=1, @@ -4469,6 +4506,53 @@ def smoke_sdxl_tag_routes() -> None: typed_hard.as_text() == sdxl_formatter._hard_tags(hard_row, pair, 1.29), "Typed SDXL pair hard tag route should match legacy wrapper output", ) + pair_stale_hard_row = _fixture_hardcore_row( + prompt="", + item="standing portrait setup", + pose="standing pose", + role_graph="", + source_role_graph="", + expression="neutral expression", + composition="standing portrait frame", + action_family="", + position_family="", + position_key="", + position_keys=[], + ) + pair_stale_root = { + "hardcore_prompt": "stale pair prompt says fully nude naked pussy penis oral anal semen penetration", + "hardcore_women_count": 1, + "hardcore_men_count": 1, + } + pair_stale_tags = sdxl_tag_routes.hard_tags_result( + sdxl_tag_routes.SDXLPairTagRequest(pair_stale_hard_row, pair_stale_root, 1.29), + deps, + ).as_text() + for forbidden in ("(naked:", "pussy", "penis", "oral sex", "anal sex", "semen", "penetration"): + _expect(forbidden not in pair_stale_tags, f"SDXL pair tags should not infer explicit tag from stale prompt: {forbidden}") + + pair_metadata_root = { + "hardcore_prompt": "stale pair prompt without explicit tag anchors", + "hardcore_women_count": 1, + "hardcore_men_count": 1, + "hardcore_clothing_state": "fully nude body, bare skin unobstructed", + } + pair_metadata_hard_row = _fixture_hardcore_row( + prompt="", + item="kneeling pose with visible penis and pussy contact", + role_graph="penis thrusts into pussy", + source_role_graph="penis thrusts into pussy", + action_family="penetration", + position_family="penetrative", + position_key="kneeling", + position_keys=["kneeling"], + ) + pair_metadata_tags = sdxl_tag_routes.hard_tags_result( + sdxl_tag_routes.SDXLPairTagRequest(pair_metadata_hard_row, pair_metadata_root, 1.29), + deps, + ).as_text() + for required in ("(naked:1.29)", "pussy", "penis", "penetration"): + _expect(required in pair_metadata_tags, f"SDXL pair tags lost structured explicit metadata tag: {required}") formatted = sdxl_formatter.format_sdxl_prompt( "", metadata_json=_json(pair),