Rebase the DSL text encoder on comfy.sd1_clip.SDClipModel and comfy.clip_model.CLIPTextModel_, replacing the old HuggingFace transformers CLIPTextModel/CLIPTextTransformer/CLIPTextEmbeddings subclasses (which are no longer how ComfyUI implements CLIP). Structure: - Merge lib/action/ into lib/actions/; drop lib/fun_clip_stuff.py and the bundled clip_config*.json (comfy ships its own) - Convert all custom_nodes.KepPromptLang.* absolute imports to relative imports so installs via Manager work regardless of install directory name Encoder: - PromptLangSDClipModel overrides encode_token_weights to walk the segment/action tree, assemble [B, seq, hidden] embeds, and call self.transformer(None, mask, embeds=..., num_tokens=...) - posScale / postPos are supported without patching the transformer by pre-baking the delta (modified - default) into embeds, so the transformer's inline add yields the modified position embedding - Drop the unused empty-baseline batch that was prepended and then sliced off; halves the per-encode forward pass for single prompts Actions: - Fix copy-pasted broken __repr__ / depth_repr across sum/diff/avg/ slerp that referenced fields that didn't exist - Fix NameError in AverageAction._validate_args (start_arg_token_length) - Fix class-level mutable state in RandAction - Unify _parse_scalar / _parse_scalar_weight / _parse_int into a single parse_numeric_arg helper in action_utils.py - Share add_with_broadcast between SumAction and DiffAction - Convert PostModifiers from TypedDict to dataclass (attribute access catches typos that .get() on string keys hides) Drop the two _exp-pooler / _exp-pooledAvg actions: they recursively invoked the HF CLIPTextTransformer and would need a rework to fit the current CLIPTextModel_ interface. They were experimental and not documented as stable. BuildGif node: - Collapse the 10-positional-arg _save_* helpers onto a small _SaveContext dataclass - Stop mutating the input arg semantics (split_every_val was reassigned to len(images) when -1) Tests: - Add pytest suite covering the parser (grammar, nesting, errors) and every action (embedding math, shape, modifier payloads) - conftest stubs ComfyUI at collection time so tests don't need a real ComfyUI install Drop the broken test_files/ scripts (CI helpers, not a test suite) and regenerate the README from tools/build_docs.py.
59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
from KepPromptLang.lib.actions.diff import DiffAction
|
|
from KepPromptLang.lib.actions.norm import NormAction
|
|
from KepPromptLang.lib.actions.sum import SumAction
|
|
from KepPromptLang.lib.parser import PromptParser
|
|
from KepPromptLang.lib.parser.prompt_segment import PromptSegment
|
|
from KepPromptLang.lib.parser.transformer import PromptTransformer
|
|
|
|
|
|
def parse(text, tokenizer):
|
|
tree = PromptParser.parse(text)
|
|
return PromptTransformer(tokenizer).transform(tree)
|
|
|
|
|
|
def test_plain_words_become_segments(tokenizer):
|
|
result = parse("hello world", tokenizer)
|
|
items = result.children
|
|
assert len(items) == 2
|
|
assert all(isinstance(i, PromptSegment) for i in items)
|
|
assert items[0].text == "hello"
|
|
assert items[1].text == "world"
|
|
|
|
|
|
def test_sum_action_parses(tokenizer):
|
|
action = parse("sum(king|man|woman)", tokenizer)
|
|
items = action.children if hasattr(action, "children") else [action]
|
|
assert len(items) == 1
|
|
assert isinstance(items[0], SumAction)
|
|
assert len(items[0].all_args) == 3
|
|
|
|
|
|
def test_nested_actions(tokenizer):
|
|
action = parse("sum(diff(king|man)|woman)", tokenizer)
|
|
items = action.children if hasattr(action, "children") else [action]
|
|
outer = items[0]
|
|
assert isinstance(outer, SumAction)
|
|
inner = outer.all_args[0][0]
|
|
assert isinstance(inner, DiffAction)
|
|
|
|
|
|
def test_norm_single_arg(tokenizer):
|
|
action = parse("norm(cat)", tokenizer)
|
|
items = action.children if hasattr(action, "children") else [action]
|
|
assert isinstance(items[0], NormAction)
|
|
|
|
|
|
def test_quoted_string(tokenizer):
|
|
result = parse('"hello world"', tokenizer)
|
|
items = result.children if hasattr(result, "children") else [result]
|
|
assert isinstance(items[0], PromptSegment)
|
|
assert items[0].text == "hello world"
|
|
|
|
|
|
def test_unknown_action_errors(tokenizer):
|
|
import pytest
|
|
from lark.exceptions import VisitError
|
|
|
|
with pytest.raises((ValueError, VisitError), match="not found in registry"):
|
|
parse("nonexistentAction(cat)", tokenizer)
|