Files
DumiFlex-ComfyUI-Wildcard-P…/tests/engine/ai/test_ai_pack_request_needs.py
T
Dumitru PicalauandClaude bfa5c43083 fix(ai): fold slots split by their own axis, drop self-appends, check rules the way they run (#120)
Pack 7's 25 Hat.type rules fired but the check read ctx['hat.type'], which
is never set (the engine keeps rolled axes in __wp_axes__), and a rule
appending a hat's own type to $hat printed 'capcap'. Pack 15 split Outfit
into Casual Outfit and Formal Outfit: both printed and their only-rows on
Footwear left no shoe, so the fallback was taken every run. A rule text
reading a variable is checked with its value filled in, and 'a hat 30% of
the time' lands on the hat only, flips an inverted share and drops a
multi-pick.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-25 00:53:35 +02:00

319 lines
16 KiB
Python

"""request_needs: what a request asks the graph for, clause by clause
(cases from the 9B smoke run, 2026-10-24)."""
from __future__ import annotations
import copy
from engine.ai import pack
from engine.ai.pack.request_needs import request_needs
from tests.engine.ai.test_ai_pack_graph import DRAWN, GRAPH
def _graph(raw, request=""):
return pack.compile_graph_plan(raw, library=[], request=request)["graph"]
def _types(raw, request):
return [(n["type"], n["refs"]) for n in request_needs(_graph(raw, request), request)]
def _slot(name, var, show="prompt", axes=(), **kw):
return {"name": name, "var": var, "what": name.lower(), "section": "", "count": 4,
"show": show, "pick": "", "hold": False, "sweep": False, "sometimes_none": 0,
"axes": [{"name": a, "values": v} for a, v in axes], **kw}
def _rel(kind, fs, fa, fv, ts, ta="", tv="", words=()):
return {"kind": kind, "from_slot": fs, "from_axis": fa, "from_value": fv, "to_slot": ts,
"to_axis": ta, "to_value": tv, "factor": 0, "var": "", "text": "",
"otherwise": "", "words": list(words)}
def test_a_rule_the_request_names_between_two_slots_with_no_relation():
raw = copy.deepcopy(DRAWN)
raw["slots"][0]["axes"] = [{"name": "tone", "values": ["vibrant", "pastel", "earthy"]}]
raw["slots"][2]["axes"] = [{"name": "season", "values": ["winter", "summer"]}]
raw["relations"] = [raw["relations"][0]] # the draw only
req = "Shirts by season, the colour from one shared list; winter never gets pastel colours."
assert ("rule_unlinked", ["Colors", "Season"]) in _types(raw, req)
# With the rule: quiet.
raw["relations"].append(_rel("exclude", "Season", "season", "winter", "Colors", "tone",
"pastel"))
assert not [t for t in _types(raw, req) if t[0] == "rule_unlinked"]
def test_the_fantasy_graph_satisfies_its_own_rules():
req = ("A fantasy character pack: race, class, an outfit and a weapon that fit the class. "
"Elves never use guns.")
assert _types(GRAPH, req) == []
def test_the_code_lands_a_share_a_hold_and_a_hidden_subject():
raw = copy.deepcopy(GRAPH)
raw["slots"].append(_slot("Hat", "hat"))
plan = pack.compile_graph_plan(raw, library=[], request="a hat about 30% of the time")
hat = next(s for s in plan["graph"]["slots"] if s["name"] == "Hat")
assert hat["sometimes_none"] == 70
assert any("Hat comes out empty in 70%" in a for a in plan["applied"])
assert _types(raw, "a hat about 30% of the time") == []
# No number: left to the planner as a finding.
assert ("sometimes_none", ["Hat"]) in _types(raw, "sometimes a hat")
raw = copy.deepcopy(GRAPH)
raw["run"] = {"mode": "story", "frames": 4}
plan = pack.compile_graph_plan(raw, library=[], request="The same race in every frame.")
assert next(s for s in plan["graph"]["slots"] if s["name"] == "Race")["hold"]
raw = copy.deepcopy(GRAPH)
raw["slots"][4]["show"] = "internal"
raw["template"]["order"] = [v for v in raw["template"]["order"] if v != "Outfit"]
plan = pack.compile_graph_plan(raw, library=[], request="Outfits by season: warm ones.")
out = next(s for s in plan["graph"]["slots"] if s["name"] == "Outfit")
assert out["show"] == "prompt" and "outfit" in plan["graph"]["template"]["order"]
# A driver the code fills from its own axis stays internal.
plan = pack.compile_graph_plan(GRAPH, library=[], request="Seasons: outfits for each.")
assert next(s for s in plan["graph"]["slots"] if s["name"] == "Season")["show"] == \
"internal"
def test_a_vague_fit_is_no_rule():
raw = copy.deepcopy(GRAPH)
raw["slots"] += [_slot("Background", "background"), _slot("Lighting", "lighting")]
assert _types(raw, "Portraits with a background and lighting that suit a portrait.") == []
def test_review_files_a_missing_rule_as_a_fix():
raw = copy.deepcopy(DRAWN)
raw["slots"][0]["axes"] = [{"name": "tone", "values": ["vibrant", "pastel"]}]
raw["slots"][2]["axes"] = [{"name": "season", "values": ["winter", "summer"]}]
raw["relations"] = [raw["relations"][0]]
req = "winter never gets pastel colours"
plan = pack.compile_graph_plan(raw, library=[], request=req)
found = [f for f in pack.review_plan(plan, [], request=req) if f["type"] == "rule_unlinked"]
assert found and found[0]["level"] == "fix"
def test_a_rule_keyed_on_a_drawn_list_is_flagged():
from engine.ai.pack.request_needs import list_reads
raw = copy.deepcopy(DRAWN)
raw["relations"].append({**_rel("derive", "Colors", "warmth", "warm", ""),
"var": "mood", "text": "vivid"})
found = list_reads(_graph(raw))
assert [f["refs"] for f in found] == [["Colors"]] and "Shirt" in found[0]["text"]
assert list_reads(_graph(DRAWN)) == []
def test_a_rule_inside_the_list_and_its_drawer_does_not_answer_another_slots_rule():
raw = copy.deepcopy(DRAWN)
raw["slots"][0]["axes"] = [{"name": "tone", "values": ["vibrant", "pastel", "earthy"]}]
raw["slots"][1]["axes"] = [{"name": "fabric", "values": ["wool", "linen"]}]
raw["slots"][2]["axes"] = [{"name": "season", "values": ["winter", "summer"]}]
raw["relations"] = [raw["relations"][0],
_rel("exclude", "Colors", "tone", "earthy", "Shirt", "fabric", "linen")]
req = "Shirts by season, the colour from one shared list; winter never gets pastel colours."
assert any(t == "rule_unlinked" and "Season" in refs for t, refs in _types(raw, req))
def test_a_draw_between_two_ruled_slots_is_no_constraint_cell():
from engine.ai.pack import graph as pg
from engine.modules.constraint_handler import ConstraintHandler
raw = copy.deepcopy(DRAWN)
raw["slots"][0]["axes"] = [{"name": "tone", "values": ["warm", "cool"]}]
raw["slots"][1]["axes"] = [{"name": "fabric", "values": ["wool", "linen"]}]
raw["relations"] = [_rel("draw", "Colors", "", "warm", "Shirt"),
_rel("exclude", "Colors", "tone", "warm", "Shirt", "fabric", "linen")]
plan = pack.compile_graph_plan(raw, library=[], request="")
built = {i["key"]: {"id": f"{n:08x}"} for n, i in enumerate(plan["items"], 1)
if i["kind"] == "wildcard"}
key = next(i["key"] for i in plan["items"] if i["kind"] == "constraint")
payload = pg._wired(plan, key, built)["payload"]
ConstraintHandler.validate_payload(payload)
assert payload["matrix"] == {"warm": {"linen": {"mode": "exclude", "factor": 0}}}
def _shirts():
raw = copy.deepcopy(DRAWN)
raw["slots"] = [
_slot("Season", "season", show="internal", axes=[("season", ["winter", "summer"])],
self_axis="season"),
_slot("Color List", "color_list", show="internal",
axes=[("palette", ["warm", "cool", "neutral"])]),
_slot("Shirt Type", "shirt_type", axes=[("season", ["winter", "summer"])]),
_slot("Shirt Color", "shirt_color", axes=[("palette", ["warm", "cool", "neutral"])])]
derive = _rel("derive", "Season", "season", "winter", "")
derive.update(var="color_rule", text="exclude pastels", otherwise="include pastels")
raw["relations"] = [_rel("agree", "Season", "season", "", "Shirt Type", "season"),
_rel("draw", "Color List", "palette", "warm", "Shirt Color"), derive]
raw["template"]["order"] = ["shirt_type", "shirt_color", "color_rule"]
return raw
def test_a_rule_on_one_side_of_the_cue_only_is_still_missing():
req = ("Shirts by season, the colour drawn from one shared colour list; winter never gets "
"pastel colours.")
assert "rule_unlinked" in [t for t, _ in _types(_shirts(), req)]
def test_a_rule_written_as_prompt_text_is_left_out():
plan = pack.compile_graph_plan(_shirts(), library=[], request="")
assert not [r for r in plan["graph"]["relations"] if r.get("var") == "color_rule"]
assert "color_rule" not in plan["graph"]["template"]["order"]
def test_a_doubled_dollar_in_the_lead_is_a_read():
raw = copy.deepcopy(DRAWN)
raw["template"]["lead"] = "$$shirt, 1girl, solo"
t = pack.compile_graph_plan(raw, library=[], request="")["graph"]["template"]
assert t["lead"].startswith("$shirt,") and "shirt" not in t["order"]
def test_an_unasked_multi_pick_between_rules_picks_one():
raw = copy.deepcopy(DRAWN)
raw["slots"][0]["pick"] = "2"
req = "shirts, each in a colour from one colour list, colours fit the season"
plan = pack.compile_graph_plan(raw, library=[], request=req)
assert next(s for s in plan["graph"]["slots"] if s["name"] == "Colors")["pick"] == ""
plan = pack.compile_graph_plan(raw, library=[], request="shirts in 2 colors that fit "
"the season")
assert next(s for s in plan["graph"]["slots"] if s["name"] == "Colors")["pick"] == "2"
def test_a_variable_name_printed_as_written_is_a_problem():
from engine.ai.pack.check import _literal_names
owned = [{"key": "i1", "var": "hero"}, {"key": "i2", "var": "scene"}]
runs = [{"hero": "a knight, $style", "scene": "rain"}]
prompts = [{"text": "comic, a knight, $style, rain"}]
assert list(_literal_names(owned, runs, prompts, "t", set())) == ["i1"]
# Printed by the template itself: filed there; an unset read is reported already.
assert list(_literal_names(owned, [{"hero": "x"}], prompts, "t", set())) == ["t"]
assert _literal_names(owned, [{"hero": "x"}], prompts, "t", {"style"}) == {}
def _shoes():
raw = copy.deepcopy(DRAWN)
raw["slots"] = [_slot("Outfit Style", "outfit_style",
axes=[("formality", ["casual", "formal"])]),
_slot("Shoe Type", "shoe_type",
axes=[("type", ["boots", "sneakers", "heels"])])]
raw["relations"] = [
_rel("only", "Outfit Style", "formality", "casual", "Shoe Type", "type", "sneakers"),
_rel("only", "Outfit Style", "formality", "formal", "Shoe Type", "type", "boots"),
_rel("only", "Outfit Style", "formality", "casual", "Shoe Type", "type", "heels"),
_rel("only", "Outfit Style", "formality", "formal", "Shoe Type", "type", "sneakers")]
raw["template"]["order"] = ["outfit_style", "shoe_type"]
return raw
def test_only_rules_the_wrong_way_round_follow_the_request():
req = ("Outfits and shoes: boots go with both casual and formal outfits, sneakers only "
"with casual, heels only with formal.")
plan = pack.compile_graph_plan(_shoes(), library=[], request=req)
pairs = {(r["from"]["value"], r["to"]["value"]) for r in plan["graph"]["relations"]}
# Boots "go with both": a row under each.
assert pairs == {("casual", "sneakers"), ("formal", "boots"), ("formal", "heels"),
("casual", "boots")}
assert any("Dropped formal → sneakers" in a for a in plan["applied"])
def test_a_rule_on_the_drawers_tag_goes_onto_the_list():
raw = copy.deepcopy(DRAWN)
raw["slots"] = [
_slot("Season", "season", show="internal", axes=[("season", ["winter", "summer"])]),
_slot("Color List", "color_list", show="internal",
axes=[("palette", ["deep", "pastel"])]),
_slot("Shirt", "shirt", axes=[("color", ["deep", "pastel"])])]
raw["relations"] = [_rel("draw", "Color List", "palette", "", "Shirt"),
_rel("exclude", "Season", "season", "winter", "Shirt", "color", "pastel")]
raw["template"]["order"] = ["shirt"]
g = pack.compile_graph_plan(raw, library=[], request="")["graph"]
ex = next(r for r in g["relations"] if r["kind"] == "exclude")
assert ex["to"] == {"slot": "Color List", "axis": "palette", "value": "pastel"}
raw["slots"][2]["axes"] = [{"name": "color", "values": ["deep_red", "soft_pastel"]}]
raw["relations"][1] = _rel("exclude", "Season", "season", "winter", "Shirt", "color",
"soft_pastel")
raw["slots"][1]["axes"] = [{"name": "palette", "values": ["deep", "soft"]}]
g = pack.compile_graph_plan(raw, library=[], request="")["graph"]
ex = next(r for r in g["relations"] if r["kind"] == "exclude")
assert ex["to"] == {"slot": "Color List", "axis": "palette", "value": "soft"}
def test_one_option_glued_from_many_phrases_is_split():
from engine.ai.pack.text_fixes import _split_glued
opts = [{"id": "o1", "value": "a heroic male warrior, a dynamic female superhero, "
"a stoic old knight, a nimble female scout", "weight": 1, "sub_categories": []}]
assert _split_glued(opts) and [o["value"] for o in opts][-1] == "a nimble female scout"
assert len({o["id"] for o in opts}) == 4
tags = [{"id": "o1", "value": "1girl, solo, smile, long hair", "weight": 1}]
assert _split_glued(tags) == [] and len(tags) == 1
def test_pairings_the_request_spells_out_become_only_rows_when_no_rule_runs():
raw = _shoes()
raw["relations"] = []
req = ("Outfits and shoes: the shoes must match the outfit (sneakers with casual, heels "
"with formal, boots with either).")
plan = pack.compile_graph_plan(raw, library=[], request=req)
pairs = {(r["from"]["value"], r["to"]["value"]) for r in plan["graph"]["relations"]
if r["kind"] == "only"}
assert pairs == {("casual", "sneakers"), ("formal", "heels"), ("casual", "boots"),
("formal", "boots")}
assert request_needs(plan["graph"], req) == []
# A rule already there: left as the planner wrote it.
raw = _shoes()
raw["relations"] = raw["relations"][:2]
plan = pack.compile_graph_plan(raw, library=[], request=req)
assert len(plan["graph"]["relations"]) == 2
def test_an_exclude_the_request_pairs_goes_and_a_shared_value_finds_the_ruled_slot():
raw = _shoes()
raw["slots"].append(_slot("Outfit", "outfit", axes=[("formality", ["casual", "formal"])]))
raw["relations"] = [
_rel("exclude", "Outfit Style", "formality", "casual", "Shoe Type", "type", "heels"),
_rel("exclude", "Outfit Style", "formality", "casual", "Shoe Type", "type", "boots"),
_rel("exclude", "Outfit Style", "formality", "formal", "Shoe Type", "type", "sneakers")]
req = ("Outfits and shoes: boots go with both casual and formal outfits, sneakers only "
"with casual, heels only with formal.")
plan = pack.compile_graph_plan(raw, library=[], request=req)
ex = {(r["from"]["value"], r["to"]["value"]) for r in plan["graph"]["relations"]
if r["kind"] == "exclude"}
assert ("casual", "boots") not in ex and ("casual", "heels") in ex
def test_a_not_list_is_a_rule_written_as_text():
raw = _shoes()
d = _rel("derive", "Outfit Style", "formality", "casual", "")
d.update(var="shoe_note", text="not heels, not pumps")
raw["relations"] = [d]
plan = pack.compile_graph_plan(raw, library=[], request="")
assert not [r for r in plan["graph"]["relations"] if r.get("var") == "shoe_note"]
def test_an_unasked_multi_pick_a_derive_keys_on_picks_one():
raw = _shoes()
raw["slots"][0]["pick"] = "2-3"
d = _rel("derive", "Outfit Style", "formality", "casual", "")
d.update(var="vibe", text="relaxed")
raw["relations"] = [d]
plan = pack.compile_graph_plan(raw, library=[], request="Outfits and shoes, long more common")
assert plan["graph"]["slots"][0]["pick"] == ""
assert any(r.get("var") == "vibe" for r in plan["graph"]["relations"])
def test_a_share_lands_on_the_thing_it_is_about_and_flips_an_inverted_one():
"""9B pack 7: "the person wears a hat about 30% of the time" left the
Person out 70% too, and a Hat at sometimes_none 30 was never fixed."""
req = "Portraits where the person wears a hat about 30% of the time."
raw = copy.deepcopy(GRAPH)
raw["slots"] += [_slot("Person", "person"), _slot("Hat", "hat")]
plan = pack.compile_graph_plan(raw, library=[], request=req)
by = {s["name"]: s for s in plan["graph"]["slots"]}
assert by["Hat"]["sometimes_none"] == 70 and by["Person"]["sometimes_none"] == 0
raw["slots"][-1].update(sometimes_none=30, pick="2")
plan = pack.compile_graph_plan(raw, library=[], request=req)
hat = next(s for s in plan["graph"]["slots"] if s["name"] == "Hat")
assert hat["sometimes_none"] == 70 and hat["pick"] == ""
plan = pack.compile_graph_plan(raw, library=[], request="Portraits, no hat 30% of the time.")
assert next(s for s in plan["graph"]["slots"] if s["name"] == "Hat")["sometimes_none"] == 30