Files
transcendedhacker-Mode_pers…/engine/composer.py
T
transcendedhacker af352d46ed Add modular prompt system with clean architecture
Introduces a scalable, production-grade prompt composition system for ComfyUI. Adds pure data libraries, schema-driven rules, a content-agnostic engine, and minimal node interface. Includes documentation (ARCHITECTURE.md, MIGRATION.md, QUICKSTART.md, README.md), three example libraries, and schema configuration for block order, weights, and model profiles.
2026-01-17 20:12:02 +05:30

93 lines
3.0 KiB
Python

"""
Pure composition engine - content agnostic
"""
import json
import os
import glob
class PromptComposer:
"""
Generic prompt composition engine.
Knows nothing about content, only follows schema rules.
"""
def __init__(self, schema_path, libraries_path):
self.schema = self._load_json(schema_path)
self.libraries = self._load_libraries(libraries_path)
def _load_json(self, path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
def _load_libraries(self, path):
libraries = {}
for json_file in glob.glob(os.path.join(path, "*.json")):
lib_id = os.path.splitext(os.path.basename(json_file))[0]
libraries[lib_id] = self._load_json(json_file)
return libraries
def get_library_names(self):
return list(self.libraries.keys())
def get_block_options(self, library_id, block_name):
if library_id not in self.libraries:
return []
blocks = self.libraries[library_id].get("blocks", {})
return list(blocks.get(block_name, {}).keys())
def apply_weight(self, text, weight, model_profile):
model_config = self.schema["models"].get(model_profile, {})
if not model_config.get("enable_weights", False):
return text
if weight == 1.0:
return text
adjusted_weight = weight * model_config.get("weight_multiplier", 1.0)
weight_format = model_config.get("weight_format", "({text}:{weight})")
return weight_format.format(text=text, weight=f"{adjusted_weight:.1f}")
def compose(self, library_id, selections, model_profile="sdxl"):
"""
Compose final prompt from selections.
Args:
library_id: Selected library
selections: Dict of {block_name: selected_option}
model_profile: "sdxl" or "flux"
Returns:
Final composed prompt string
"""
library = self.libraries.get(library_id, {})
blocks = library.get("blocks", {})
structure = self.schema["structure"]
weights = self.schema["weights"]
parts = []
block_order = structure["block_order"]
separator = structure["separator"]
break_positions = structure.get("break_positions", [])
for i, block_name in enumerate(block_order):
selected_option = selections.get(block_name)
if not selected_option:
continue
block_data = blocks.get(block_name, {})
text = block_data.get(selected_option, "")
if not text:
continue
weight = weights.get(block_name, 1.0)
weighted_text = self.apply_weight(text, weight, model_profile)
parts.append(weighted_text)
if (i + 1) in break_positions:
parts.append("BREAK")
return separator.join(parts)